优化项目配置和页面结构
- 更新settings.local.json,移除冗余的权限设置,添加新的Playwright相关权限。 - 删除exhibition_demo_project_2025.md文档,清理不再使用的文件。 - 更新多个HTML页面,统一viewport设置,添加页面加载动画、错误处理和性能优化脚本。 - 统一使用Tailwind CSS的引入方式,提升页面加载性能。 - 增强导航组件,支持移动端菜单和返回顶部功能,改善用户体验。
This commit is contained in:
@@ -1,85 +1,22 @@
|
|||||||
{
|
{
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"Bash(mkdir:*)",
|
|
||||||
"Bash(rm:*)",
|
|
||||||
"Bash(pip install:*)",
|
|
||||||
"Bash(python3:*)",
|
|
||||||
"Bash(source:*)",
|
|
||||||
"Bash(/usr/bin/python3:*)",
|
|
||||||
"Bash(python:*)",
|
|
||||||
"Bash(cat:*)",
|
|
||||||
"mcp__promptx__promptx_init",
|
|
||||||
"mcp__claude-code-mcp__claude_code",
|
|
||||||
"mcp__serena__activate_project",
|
|
||||||
"mcp__serena__check_onboarding_performed",
|
|
||||||
"mcp__serena__onboarding",
|
|
||||||
"mcp__serena__list_dir",
|
|
||||||
"mcp__serena__read_file",
|
|
||||||
"mcp__serena__search_for_pattern",
|
|
||||||
"mcp__serena__execute_shell_command",
|
|
||||||
"mcp__serena__think_about_collected_information",
|
|
||||||
"mcp__serena__write_memory",
|
|
||||||
"mcp__serena__list_memories",
|
|
||||||
"Bash(pip3 install:*)",
|
|
||||||
"Bash(chmod:*)",
|
|
||||||
"Bash(grep:*)",
|
|
||||||
"Bash(pip install pymysql pyyaml requests)",
|
|
||||||
"Bash(python3:*)",
|
|
||||||
"Bash(grep:*)",
|
|
||||||
"Bash(cat:*)",
|
|
||||||
"Bash(mv:*)",
|
|
||||||
"Bash(timeout 120 python src/test/2025-08-14_模型对比测试/model_comparison_test.py)",
|
|
||||||
"Bash(timeout 60 python src/test/2025-08-14_模型对比测试/test_deepseek_professional.py)",
|
|
||||||
"mcp__serena__create_text_file",
|
|
||||||
"mcp__serena__replace_regex",
|
|
||||||
"Bash(mysql:*)",
|
|
||||||
"Bash(tree:*)",
|
|
||||||
"Bash(pkill:*)",
|
|
||||||
"mcp__serena__replace_symbol_body",
|
|
||||||
"Bash(timeout 600 python3 src/就业管家首页/main.py)",
|
|
||||||
"mcp__serena__find_file",
|
|
||||||
"Bash(/Users/xiaoqi/Documents/Dev/Project/2025-08-12_Saas数据生成/venv/bin/python3 /Users/xiaoqi/Documents/Dev/Project/2025-08-12_Saas数据生成/src/就业管家首页/main.py)",
|
|
||||||
"Bash(/Users/xiaoqi/Documents/Dev/Project/2025-08-12_Saas数据生成/venv/bin/python3 run_full_generation.py)",
|
|
||||||
"Bash(/Users/xiaoqi/Documents/Dev/Project/2025-08-12_Saas数据生成/run.sh)",
|
|
||||||
"Bash(. venv/bin/activate)",
|
|
||||||
"Bash(./venv/bin/python3:*)",
|
|
||||||
"Bash(/Users/xiaoqi/Documents/Dev/Project/2025-08-12_Saas数据生成/venv/bin/python3 src/就业管家首页/main.py)",
|
|
||||||
"Bash(/Users/xiaoqi/Documents/Dev/Project/2025-08-12_Saas数据生成/venv/bin/python3:*)",
|
|
||||||
"mcp__sequential-thinking__sequentialthinking",
|
|
||||||
"Bash(test:*)",
|
|
||||||
"Bash(pnpm install:*)",
|
|
||||||
"Bash(pnpm:*)",
|
|
||||||
"Bash(nvm list:*)",
|
|
||||||
"Bash(export PNPM_HOME=\"/Users/xiaoqi/Library/pnpm\")",
|
|
||||||
"Bash(export PATH=\"$PNPM_HOME:$PATH\")",
|
|
||||||
"Bash(brew upgrade:*)",
|
|
||||||
"Bash(npm install:*)",
|
|
||||||
"Bash(git add:*)",
|
|
||||||
"Bash(export N8N_DEFAULT_LOCALE=zh-CN)",
|
|
||||||
"Bash(git pull:*)",
|
|
||||||
"Bash(git merge:*)",
|
|
||||||
"Bash(git checkout:*)",
|
|
||||||
"Bash(git stash:*)",
|
|
||||||
"Bash(git commit:*)",
|
|
||||||
"Bash(npm run dev:*)",
|
|
||||||
"Bash(npm run build:*)",
|
|
||||||
"Bash(npm run preview:*)",
|
|
||||||
"Bash(open http://localhost:4173)",
|
|
||||||
"Bash(find:*)",
|
|
||||||
"Bash(open index.html)",
|
|
||||||
"Bash(git push:*)",
|
|
||||||
"Bash(sed:*)",
|
|
||||||
"Bash(/Users/xiaoqi/Documents/Dev/Project/2025-09-08_n8nDEMO演示/n8n-n8n-1.109.2/start.bat)",
|
|
||||||
"Bash(curl:*)",
|
|
||||||
"Bash(node server.js)",
|
|
||||||
"mcp__browser-tools__takeScreenshot",
|
"mcp__browser-tools__takeScreenshot",
|
||||||
"Bash(for file in pages/exhibition.html pages/marketing.html pages/operation.html pages/budget.html pages/risk.html)",
|
|
||||||
"Bash(do sed -i '' 's|</body>| <script src=\"\"../js/nav-component.js\"\"></script>\\n</body>|' \"$file\")",
|
|
||||||
"Bash(done)",
|
|
||||||
"mcp__playwright__browser_navigate",
|
"mcp__playwright__browser_navigate",
|
||||||
"mcp__playwright__browser_take_screenshot"
|
"mcp__playwright__browser_wait_for",
|
||||||
|
"mcp__playwright__browser_click",
|
||||||
|
"mcp__playwright__browser_snapshot",
|
||||||
|
"mcp__playwright__browser_evaluate",
|
||||||
|
"mcp__browser-tools__getConsoleErrors",
|
||||||
|
"mcp__playwright__browser_console_messages",
|
||||||
|
"mcp__playwright__browser_close",
|
||||||
|
"Bash(for file in exhibition.html marketing.html operation.html overview.html)",
|
||||||
|
"Bash(do sed -i '' 's|<link href=\"\"https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css\"\" rel=\"\"stylesheet\"\">|<script src=\"\"https://cdn.tailwindcss.com\"\"></script>|g' \"$file\")",
|
||||||
|
"Bash(done)",
|
||||||
|
"mcp__serena__delete_memory"
|
||||||
],
|
],
|
||||||
|
"deny": [],
|
||||||
|
"ask": [],
|
||||||
"defaultMode": "acceptEdits",
|
"defaultMode": "acceptEdits",
|
||||||
"additionalDirectories": [
|
"additionalDirectories": [
|
||||||
"/Users/xiaoqi/Documents"
|
"/Users/xiaoqi/Documents"
|
||||||
|
|||||||
82
.serena/memories/duoduo_agent_design_system.md
Normal file
82
.serena/memories/duoduo_agent_design_system.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# Duoduo Agent 智能设计系统
|
||||||
|
|
||||||
|
## 系统概述
|
||||||
|
Duoduo Agent是一个AI驱动的展会布局设计系统,通过深度学习实现从需求到方案的智能转化。
|
||||||
|
|
||||||
|
## 核心能力
|
||||||
|
|
||||||
|
### 1. 技术指标
|
||||||
|
- **交付时间**:20小时(传统2周)
|
||||||
|
- **设计准确率**:95%
|
||||||
|
- **成本节约**:80%
|
||||||
|
- **一次通过率**:95%
|
||||||
|
- **规范符合率**:99.9%
|
||||||
|
|
||||||
|
### 2. 设计流程
|
||||||
|
|
||||||
|
#### 阶段1:智能空间分析
|
||||||
|
- NLP需求解析
|
||||||
|
- 3D空间建模
|
||||||
|
- 规范自动校验
|
||||||
|
- 遗传算法优化
|
||||||
|
- 处理时间:30秒
|
||||||
|
|
||||||
|
#### 阶段2:智能设计生成
|
||||||
|
- 并行生成5-10个方案
|
||||||
|
- 品牌VI风格迁移
|
||||||
|
- 实时成本控制
|
||||||
|
- 蒙特卡洛人流仿真
|
||||||
|
- 处理时间:15分钟
|
||||||
|
|
||||||
|
#### 阶段3:数字孪生建模
|
||||||
|
- 物理环境仿真
|
||||||
|
- VR沉浸式预览
|
||||||
|
- 8K超高清渲染
|
||||||
|
- 实时光线追踪
|
||||||
|
- 材料真实映射
|
||||||
|
|
||||||
|
#### 阶段4:完整交付
|
||||||
|
- CAD施工图纸
|
||||||
|
- 精确物料清单
|
||||||
|
- 分步施工指南
|
||||||
|
- 透明预算报表
|
||||||
|
- 100%可执行率
|
||||||
|
|
||||||
|
## 技术架构
|
||||||
|
|
||||||
|
### 深度学习引擎
|
||||||
|
- 10万+展会案例训练
|
||||||
|
- 知识图谱匹配
|
||||||
|
- 多目标优化求解
|
||||||
|
- 实时迭代优化
|
||||||
|
|
||||||
|
### 设计工具集成
|
||||||
|
- AutoCAD 2024
|
||||||
|
- 3DS Max + V-Ray
|
||||||
|
- Revit BIM协同
|
||||||
|
- 5000+ PBR材质库
|
||||||
|
|
||||||
|
### 智能算法
|
||||||
|
- 空间利用率优化
|
||||||
|
- 人流动线模拟
|
||||||
|
- 成本效果平衡
|
||||||
|
- 消防建筑规范校验
|
||||||
|
|
||||||
|
## 成功案例
|
||||||
|
- 500+ 展会项目
|
||||||
|
- 50+ 世界500强客户
|
||||||
|
- 20+ 国家级展会
|
||||||
|
- 98% 客户满意度
|
||||||
|
|
||||||
|
## 竞争优势
|
||||||
|
|
||||||
|
1. **精准度高**:基于海量数据持续优化
|
||||||
|
2. **快速迭代**:实时修改,秒级响应
|
||||||
|
3. **成本可控**:智能材料选择,预算监控
|
||||||
|
4. **合规保障**:自动对标国家标准
|
||||||
|
|
||||||
|
## API集成
|
||||||
|
- RESTful API接口
|
||||||
|
- 企业系统无缝对接
|
||||||
|
- 24/7实时服务
|
||||||
|
- 多格式文件导出
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
# 会展策划多Agent演示系统项目 - 2025-09-08
|
|
||||||
|
|
||||||
## 项目概述
|
|
||||||
构建一个基于React的Web演示系统,实时展示多Agent协同生成会展策划方案的完整过程。
|
|
||||||
|
|
||||||
## 技术栈决策
|
|
||||||
- **框架**: React 18 + TypeScript
|
|
||||||
- **动画**: Framer Motion + CSS Animations
|
|
||||||
- **样式**: Tailwind CSS (参考字节跳动、Flowith、Raycast设计风格)
|
|
||||||
- **状态管理**: Zustand
|
|
||||||
- **构建工具**: Vite
|
|
||||||
|
|
||||||
## 项目文件结构
|
|
||||||
```
|
|
||||||
web_frontend/
|
|
||||||
├── data/
|
|
||||||
│ ├── Agent_prompt/ # 7个专业Agent的prompt文档
|
|
||||||
│ │ ├── 信息检索专家.md
|
|
||||||
│ │ ├── 格式编辑专家.md
|
|
||||||
│ │ ├── 会展策划专家.md
|
|
||||||
│ │ ├── 设计师专家.md
|
|
||||||
│ │ ├── 财务预算专家.md
|
|
||||||
│ │ ├── 活动执行专家.md
|
|
||||||
│ │ └── 营销宣传专家.md
|
|
||||||
│ └── 会展策划/
|
|
||||||
│ └── 汽车展会展策划案_DEMO.md # 演示输出文档
|
|
||||||
├── doc/
|
|
||||||
│ ├── 可行性分析.md # 技术实施方案
|
|
||||||
│ └── 会展策划工作流.md # n8n工作流程图
|
|
||||||
└── exhibition-demo/ # React演示系统
|
|
||||||
```
|
|
||||||
|
|
||||||
## 演示参数
|
|
||||||
- **总时长**: 3分钟
|
|
||||||
- **文字生成速度**: 30-40字/秒
|
|
||||||
- **Agent Prompt展示**: 完整展示,逐步显示
|
|
||||||
- **交互控制**: 暂时不支持跳过,保证演示完整性
|
|
||||||
|
|
||||||
## 内容板块(6个独立页面)
|
|
||||||
1. 策划案
|
|
||||||
2. 展会介绍与预期效果
|
|
||||||
3. 营销方案
|
|
||||||
4. 现场运营方案
|
|
||||||
5. 预算与收益分析
|
|
||||||
6. 风险评估与应急预案
|
|
||||||
|
|
||||||
## 演示流程设计
|
|
||||||
1. 初始页面:展示Run按钮
|
|
||||||
2. 点击Run:开始执行mermaid流程
|
|
||||||
3. 每个Agent独立输出:
|
|
||||||
- 显示Agent头像和名称
|
|
||||||
- 展示Agent的Prompt
|
|
||||||
- 逐字生成文字内容(打字机效果)
|
|
||||||
- 图片加载时显示loading动画
|
|
||||||
4. 生成完成后自动切换到下一个页面
|
|
||||||
|
|
||||||
## 设计风格
|
|
||||||
- **视觉风格**: 简洁实用、年轻化、高端感
|
|
||||||
- **参考产品**: 火山引擎、Trea、Flowith、Raycast
|
|
||||||
- **核心特征**:
|
|
||||||
- 精致的微动画
|
|
||||||
- 清晰的信息层级
|
|
||||||
- 中性色调搭配品牌色
|
|
||||||
- 优雅的过渡效果
|
|
||||||
|
|
||||||
## Git分支管理
|
|
||||||
- **Main**: 主分支(已同步到Gitea)
|
|
||||||
- **My_N8N**: n8n中文翻译功能
|
|
||||||
- **Web_Create**: AgentDemo演示系统开发(当前分支)
|
|
||||||
86
.serena/memories/exhibition_web_frontend_2025.md
Normal file
86
.serena/memories/exhibition_web_frontend_2025.md
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# 展会Web前端项目记忆 - 2025年更新
|
||||||
|
|
||||||
|
## 项目概览
|
||||||
|
- 项目路径:`/web_frontend/web_result/`
|
||||||
|
- 主题:新能源汽车产业博览会(NEVIT 2024)
|
||||||
|
- 技术栈:HTML5 + Tailwind CSS + GSAP动画 + Vue组件化
|
||||||
|
|
||||||
|
## 核心页面结构
|
||||||
|
|
||||||
|
### 1. 导航系统 (nav-component.js)
|
||||||
|
- 统一导航组件,自动适配页面路径
|
||||||
|
- 导航顺序:首页 → 展会概览 → 展览内容 → 布局设计 → 营销推广 → 预算分析 → 风险评估
|
||||||
|
- 移动端响应式菜单
|
||||||
|
|
||||||
|
### 2. 布局设计页面 (operation.html) - 重点页面
|
||||||
|
**主题**:AI驱动的展会布局设计系统
|
||||||
|
|
||||||
|
**页面结构**:
|
||||||
|
1. Hero区:AI布局设计介绍,关键指标展示
|
||||||
|
2. AI设计原理:三步流程(需求理解→方案生成→智能优化)
|
||||||
|
3. Duoduo Agent系统:5阶段横向滚动展示
|
||||||
|
- 智能空间分析
|
||||||
|
- 智能设计生成
|
||||||
|
- 数字孪生建模
|
||||||
|
- 完整交付方案
|
||||||
|
4. 设计工具链:CAD、3D建模、BIM协同、智能算法
|
||||||
|
5. 成功案例展示
|
||||||
|
|
||||||
|
**技术特点**:
|
||||||
|
- GSAP ScrollTrigger横向滚动
|
||||||
|
- 深色主题设计系统
|
||||||
|
- Glass morphism效果
|
||||||
|
- 性能优化(避免过度动画)
|
||||||
|
|
||||||
|
### 3. 图片资源
|
||||||
|
- 路径:`/web_frontend/web_result/data/会展策划/image/`
|
||||||
|
- 页面引用:`../data/会展策划/image/`
|
||||||
|
- 主要图片:Whisk系列JPG文件
|
||||||
|
|
||||||
|
## 设计系统
|
||||||
|
|
||||||
|
### 颜色变量
|
||||||
|
```css
|
||||||
|
--bg-primary: #0a0a0a;
|
||||||
|
--bg-secondary: #111111;
|
||||||
|
--accent: #646cff;
|
||||||
|
--text-primary: #ffffff;
|
||||||
|
--text-secondary: #a1a1aa;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 关键样式
|
||||||
|
- Glass效果:`bg-white/5 backdrop-blur-lg border-white/10`
|
||||||
|
- 渐变文字:`gradient-text` (emerald到blue渐变)
|
||||||
|
- 卡片悬浮:`hover:transform hover:scale-105`
|
||||||
|
|
||||||
|
## 常见问题及解决方案
|
||||||
|
|
||||||
|
### 1. 图片路径
|
||||||
|
- 从pages目录访问:使用`../data/`而非`../../data/`
|
||||||
|
- 缺失图片替换:使用备用的Whisk系列图片
|
||||||
|
|
||||||
|
### 2. GSAP滚动优化
|
||||||
|
- 避免滚动结束跳动:使用精确的end计算
|
||||||
|
- 性能优化:减少will-change使用,避免过度动画
|
||||||
|
- 移动端适配:使用matchMedia区分处理
|
||||||
|
|
||||||
|
### 3. 导航栏统一
|
||||||
|
- 所有页面引入nav-component.js
|
||||||
|
- 自动处理路径差异(首页vs子页面)
|
||||||
|
- 统一样式和交互
|
||||||
|
|
||||||
|
## 开发规范
|
||||||
|
|
||||||
|
1. **性能优先**:避免过度动画和复杂效果
|
||||||
|
2. **响应式设计**:移动端优先,渐进增强
|
||||||
|
3. **组件复用**:导航、返回顶部等通用组件
|
||||||
|
4. **图片优化**:使用lazy loading,合理压缩
|
||||||
|
5. **代码组织**:JS组件化,CSS模块化
|
||||||
|
|
||||||
|
## 最近更新 (2025-01-09)
|
||||||
|
|
||||||
|
1. 重构operation.html为AI布局设计主题
|
||||||
|
2. 优化GSAP横向滚动性能
|
||||||
|
3. 修复图片路径问题
|
||||||
|
4. 统一导航组件系统
|
||||||
|
5. 实现深色主题设计语言
|
||||||
23
n8n-n8n-1.109.2/n8n-20250910-022535.log
Normal file
23
n8n-n8n-1.109.2/n8n-20250910-022535.log
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
> n8n-monorepo@1.109.2 start /Users/xiaoqi/Documents/Dev/Project/2025-09-08_n8nDEMO演示/n8n-n8n-1.109.2
|
||||||
|
> run-script-os
|
||||||
|
|
||||||
|
|
||||||
|
> n8n-monorepo@1.109.2 start:default
|
||||||
|
> cd packages/cli/bin && ./n8n
|
||||||
|
|
||||||
|
Permissions 0644 for n8n settings file /Users/xiaoqi/.n8n/config are too wide. This is ignored for now, but in the future n8n will attempt to change the permissions automatically. To automatically enforce correct permissions now set N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true (recommended), or turn this check off set N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=false.
|
||||||
|
Initializing n8n process
|
||||||
|
n8n ready on ::, port 5678
|
||||||
|
n8n Task Broker ready on 127.0.0.1, port 5679
|
||||||
|
Initializing AuthRolesService...
|
||||||
|
AuthRolesService initialized successfully.
|
||||||
|
[license SDK] Skipping renewal on init: license cert is not initialized
|
||||||
|
Registered runner "JS Task Runner" (MUfnTJM56msr0saFg9y96)
|
||||||
|
Version: 1.109.2
|
||||||
|
Locale: zh-CN
|
||||||
|
|
||||||
|
Editor is now accessible via:
|
||||||
|
http://localhost:5678
|
||||||
|
(node:40550) [DEP0060] DeprecationWarning: The `util._extend` API is deprecated. Please use Object.assign() instead.
|
||||||
|
(Use `node --trace-deprecation ...` to show where the warning was created)
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes, viewport-fit=cover">
|
||||||
<title>2024长三角国际新能源汽车与智能交通产业博览会</title>
|
<title>2024长三角国际新能源汽车与智能交通产业博览会</title>
|
||||||
|
|
||||||
<!-- 预加载关键资源 -->
|
<!-- 预加载关键资源 -->
|
||||||
@@ -14,11 +14,23 @@
|
|||||||
<link rel="stylesheet" href="css/animations.css">
|
<link rel="stylesheet" href="css/animations.css">
|
||||||
|
|
||||||
<!-- Tailwind CSS -->
|
<!-- Tailwind CSS -->
|
||||||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
|
||||||
<!-- 字体 -->
|
<!-- 字体 -->
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=Noto+Sans+SC:wght@300;400;500;700;900&family=Orbitron:wght@400;700;900&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=Noto+Sans+SC:wght@300;400;500;700;900&family=Orbitron:wght@400;700;900&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<!-- 错误处理(需要最早加载) -->
|
||||||
|
<script src="js/error-handler.js"></script>
|
||||||
|
|
||||||
|
<!-- 页面加载动画(需要尽早加载) -->
|
||||||
|
<script src="js/page-loader.js"></script>
|
||||||
|
|
||||||
|
<!-- 移动端优化 -->
|
||||||
|
<script src="js/mobile-optimize.js"></script>
|
||||||
|
|
||||||
|
<!-- 性能优化 -->
|
||||||
|
<script src="js/performance-optimizer.js"></script>
|
||||||
|
|
||||||
<!-- Font Awesome 图标 -->
|
<!-- Font Awesome 图标 -->
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
<style>
|
<style>
|
||||||
@@ -103,18 +115,7 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-50 loading">
|
<body class="bg-gray-50 loading">
|
||||||
<!-- 页面加载器 -->
|
<!-- 页面加载器将由page-loader.js动态创建 -->
|
||||||
<div class="page-loader" id="pageLoader">
|
|
||||||
<div class="loader-content">
|
|
||||||
<div class="loader-logo">
|
|
||||||
<i class="fas fa-car text-4xl text-emerald-500"></i>
|
|
||||||
</div>
|
|
||||||
<div class="loader-text">加载中...</div>
|
|
||||||
<div class="loader-progress">
|
|
||||||
<div class="loader-progress-bar"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Navigation -->
|
<!-- Navigation -->
|
||||||
<nav class="fixed top-0 w-full glass-morphism shadow-md z-50 transition-all duration-300" id="navbar">
|
<nav class="fixed top-0 w-full glass-morphism shadow-md z-50 transition-all duration-300" id="navbar">
|
||||||
@@ -560,6 +561,7 @@
|
|||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script src="js/nav-component.js"></script>
|
<script src="js/nav-component.js"></script>
|
||||||
|
<script src="js/back-to-top.js"></script>
|
||||||
<script src="js/main.js"></script>
|
<script src="js/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
188
web_frontend/web_result/js/back-to-top.js
Normal file
188
web_frontend/web_result/js/back-to-top.js
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
// 返回顶部按钮组件
|
||||||
|
(function() {
|
||||||
|
// 创建返回顶部按钮HTML
|
||||||
|
function createBackToTopButton() {
|
||||||
|
const button = document.createElement('div');
|
||||||
|
button.id = 'back-to-top';
|
||||||
|
button.className = 'fixed bottom-8 right-8 z-40 opacity-0 pointer-events-none transition-all duration-300';
|
||||||
|
button.innerHTML = `
|
||||||
|
<button class="back-to-top-btn group relative w-12 h-12 bg-gradient-to-br from-emerald-400 to-blue-500 rounded-full shadow-lg hover:shadow-xl transform hover:scale-110 transition-all duration-300">
|
||||||
|
<span class="absolute inset-0 rounded-full bg-white opacity-0 group-hover:opacity-10 transition-opacity duration-300"></span>
|
||||||
|
<svg class="w-6 h-6 text-white absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
|
||||||
|
fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18"></path>
|
||||||
|
</svg>
|
||||||
|
<span class="absolute -top-10 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-nowrap">
|
||||||
|
返回顶部
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
document.body.appendChild(button);
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加样式
|
||||||
|
function addStyles() {
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = `
|
||||||
|
#back-to-top.show {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#back-to-top:not(.show) {
|
||||||
|
transform: translateY(100px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-to-top-btn {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-to-top-btn::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
|
||||||
|
transition: left 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-to-top-btn:hover::before {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-to-top-btn svg {
|
||||||
|
animation: float 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float {
|
||||||
|
0%, 100% {
|
||||||
|
transform: translateX(-50%) translateY(-50%);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateX(-50%) translateY(calc(-50% - 3px));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端适配 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
#back-to-top {
|
||||||
|
bottom: 1.5rem;
|
||||||
|
right: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-to-top-btn {
|
||||||
|
width: 2.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-to-top-btn svg {
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 平滑滚动行为 */
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 防止按钮与其他元素冲突 */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
#back-to-top {
|
||||||
|
bottom: 5rem; /* 避免与移动端菜单按钮重叠 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 滚动到顶部功能
|
||||||
|
function scrollToTop() {
|
||||||
|
// 使用 smooth 滚动
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 可选:添加动画反馈
|
||||||
|
const button = document.querySelector('.back-to-top-btn');
|
||||||
|
if (button) {
|
||||||
|
button.style.transform = 'scale(0.9)';
|
||||||
|
setTimeout(() => {
|
||||||
|
button.style.transform = '';
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 控制按钮显示/隐藏
|
||||||
|
function handleScroll() {
|
||||||
|
const button = document.getElementById('back-to-top');
|
||||||
|
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
||||||
|
const windowHeight = window.innerHeight;
|
||||||
|
|
||||||
|
// 当滚动超过一屏高度时显示按钮
|
||||||
|
if (scrollTop > windowHeight * 0.5) {
|
||||||
|
button.classList.add('show');
|
||||||
|
} else {
|
||||||
|
button.classList.remove('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可选:根据滚动位置调整按钮透明度
|
||||||
|
if (scrollTop > windowHeight) {
|
||||||
|
const maxScroll = document.documentElement.scrollHeight - windowHeight;
|
||||||
|
const scrollProgress = Math.min(scrollTop / maxScroll, 1);
|
||||||
|
const opacity = 0.5 + scrollProgress * 0.5; // 从50%到100%透明度
|
||||||
|
button.style.opacity = button.classList.contains('show') ? opacity : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
function init() {
|
||||||
|
// 添加样式
|
||||||
|
addStyles();
|
||||||
|
|
||||||
|
// 创建按钮
|
||||||
|
const button = createBackToTopButton();
|
||||||
|
|
||||||
|
// 添加点击事件
|
||||||
|
button.addEventListener('click', scrollToTop);
|
||||||
|
|
||||||
|
// 添加滚动监听(使用节流)
|
||||||
|
let scrollTimer;
|
||||||
|
window.addEventListener('scroll', () => {
|
||||||
|
if (scrollTimer) {
|
||||||
|
clearTimeout(scrollTimer);
|
||||||
|
}
|
||||||
|
scrollTimer = setTimeout(handleScroll, 50);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始检查
|
||||||
|
handleScroll();
|
||||||
|
|
||||||
|
// 键盘快捷键支持(按 'T' 键返回顶部)
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
// 排除输入框等元素
|
||||||
|
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按 T 键或 Home 键返回顶部
|
||||||
|
if (e.key === 't' || e.key === 'T' || e.key === 'Home') {
|
||||||
|
e.preventDefault();
|
||||||
|
scrollToTop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// DOM加载完成后初始化
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
|
} else {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
})();
|
||||||
279
web_frontend/web_result/js/error-handler.js
Normal file
279
web_frontend/web_result/js/error-handler.js
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
// 错误处理和兼容性脚本
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// 全局错误处理
|
||||||
|
window.addEventListener('error', function(event) {
|
||||||
|
console.error('Global error:', {
|
||||||
|
message: event.message,
|
||||||
|
filename: event.filename,
|
||||||
|
lineno: event.lineno,
|
||||||
|
colno: event.colno,
|
||||||
|
error: event.error
|
||||||
|
});
|
||||||
|
|
||||||
|
// 发送错误到控制台(便于调试)
|
||||||
|
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
|
||||||
|
console.group('Error Details:');
|
||||||
|
console.log('Message:', event.message);
|
||||||
|
console.log('File:', event.filename);
|
||||||
|
console.log('Line:', event.lineno + ':' + event.colno);
|
||||||
|
if (event.error && event.error.stack) {
|
||||||
|
console.log('Stack:', event.error.stack);
|
||||||
|
}
|
||||||
|
console.groupEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // 阻止默认错误处理
|
||||||
|
});
|
||||||
|
|
||||||
|
// Promise 错误处理
|
||||||
|
window.addEventListener('unhandledrejection', function(event) {
|
||||||
|
console.error('Unhandled promise rejection:', event.reason);
|
||||||
|
|
||||||
|
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
|
||||||
|
console.group('Promise Rejection:');
|
||||||
|
console.log('Reason:', event.reason);
|
||||||
|
if (event.reason && event.reason.stack) {
|
||||||
|
console.log('Stack:', event.reason.stack);
|
||||||
|
}
|
||||||
|
console.groupEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault(); // 阻止默认处理
|
||||||
|
});
|
||||||
|
|
||||||
|
// DOM 查询安全包装
|
||||||
|
function safeQuerySelector(selector) {
|
||||||
|
try {
|
||||||
|
return document.querySelector(selector);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Invalid selector:', selector, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeQuerySelectorAll(selector) {
|
||||||
|
try {
|
||||||
|
return document.querySelectorAll(selector);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Invalid selector:', selector, e);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 安全的getElementById
|
||||||
|
function safeGetElementById(id) {
|
||||||
|
try {
|
||||||
|
return document.getElementById(id);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Invalid element ID:', id, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修复常见的兼容性问题
|
||||||
|
function fixCompatibilityIssues() {
|
||||||
|
// 修复classList不支持的情况(简化版本以避免Illegal invocation)
|
||||||
|
try {
|
||||||
|
// 安全检测classList支持
|
||||||
|
var testElement = document.createElement('div');
|
||||||
|
if (!testElement.classList) {
|
||||||
|
// 简单的classList polyfill
|
||||||
|
window.ClassListPolyfill = {
|
||||||
|
addClass: function(element, className) {
|
||||||
|
if (element && className) {
|
||||||
|
var classes = element.className ? element.className.split(' ') : [];
|
||||||
|
if (classes.indexOf(className) === -1) {
|
||||||
|
classes.push(className);
|
||||||
|
element.className = classes.join(' ').trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeClass: function(element, className) {
|
||||||
|
if (element && className) {
|
||||||
|
var classes = element.className ? element.className.split(' ') : [];
|
||||||
|
var index = classes.indexOf(className);
|
||||||
|
if (index !== -1) {
|
||||||
|
classes.splice(index, 1);
|
||||||
|
element.className = classes.join(' ').trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hasClass: function(element, className) {
|
||||||
|
if (!element || !className) return false;
|
||||||
|
var classes = element.className ? element.className.split(' ') : [];
|
||||||
|
return classes.indexOf(className) !== -1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 如果检测失败,忽略错误
|
||||||
|
console.warn('classList detection failed:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修复addEventListener不支持的情况
|
||||||
|
if (!Element.prototype.addEventListener && Element.prototype.attachEvent) {
|
||||||
|
Element.prototype.addEventListener = function(event, listener) {
|
||||||
|
this.attachEvent('on' + event, listener);
|
||||||
|
};
|
||||||
|
Element.prototype.removeEventListener = function(event, listener) {
|
||||||
|
this.detachEvent('on' + event, listener);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修复Array.from不支持的情况
|
||||||
|
if (!Array.from) {
|
||||||
|
Array.from = function(arrayLike) {
|
||||||
|
var result = [];
|
||||||
|
for (var i = 0; i < arrayLike.length; i++) {
|
||||||
|
result.push(arrayLike[i]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修复forEach不支持的情况
|
||||||
|
if (!NodeList.prototype.forEach) {
|
||||||
|
NodeList.prototype.forEach = function(callback, thisArg) {
|
||||||
|
for (var i = 0; i < this.length; i++) {
|
||||||
|
callback.call(thisArg, this[i], i, this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测浏览器支持情况
|
||||||
|
function detectBrowserSupport() {
|
||||||
|
const features = {
|
||||||
|
es6: {
|
||||||
|
arrow: false,
|
||||||
|
const: false,
|
||||||
|
let: false,
|
||||||
|
template: false,
|
||||||
|
destructuring: false
|
||||||
|
},
|
||||||
|
dom: {
|
||||||
|
classList: (function() {
|
||||||
|
try {
|
||||||
|
var testElement = document.createElement('div');
|
||||||
|
return !!testElement.classList;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
querySelector: !!document.querySelector,
|
||||||
|
addEventListener: !!window.addEventListener
|
||||||
|
},
|
||||||
|
css: {
|
||||||
|
grid: typeof CSS !== 'undefined' && CSS.supports && CSS.supports('display', 'grid'),
|
||||||
|
flexbox: typeof CSS !== 'undefined' && CSS.supports && CSS.supports('display', 'flex'),
|
||||||
|
transform: typeof CSS !== 'undefined' && CSS.supports && CSS.supports('transform', 'translateX(0)')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 测试ES6功能
|
||||||
|
try {
|
||||||
|
eval('const x = 1');
|
||||||
|
features.es6.const = true;
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
eval('let x = 1');
|
||||||
|
features.es6.let = true;
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
eval('`template`');
|
||||||
|
features.es6.template = true;
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
eval('() => {}');
|
||||||
|
features.es6.arrow = true;
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
|
||||||
|
console.group('Browser Support:');
|
||||||
|
console.table(features);
|
||||||
|
console.groupEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
return features;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修复具体的错误
|
||||||
|
function fixSpecificErrors() {
|
||||||
|
// 确保所有必要的元素存在
|
||||||
|
const requiredElements = [
|
||||||
|
'navbar',
|
||||||
|
'mobile-menu-button',
|
||||||
|
'mobile-menu',
|
||||||
|
'mobile-menu-overlay',
|
||||||
|
'close-menu'
|
||||||
|
];
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
requiredElements.forEach(function(id) {
|
||||||
|
const element = document.getElementById(id);
|
||||||
|
if (!element) {
|
||||||
|
console.warn('Missing required element:', id);
|
||||||
|
|
||||||
|
// 尝试创建缺失的元素(某些情况下)
|
||||||
|
if (id === 'mobile-menu-overlay' && !document.getElementById(id)) {
|
||||||
|
const overlay = document.createElement('div');
|
||||||
|
overlay.id = id;
|
||||||
|
overlay.className = 'mobile-menu-overlay hidden';
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
console.log('Created missing overlay element');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 安全的脚本加载
|
||||||
|
function safeLoadScript(src, callback) {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = src;
|
||||||
|
script.onload = function() {
|
||||||
|
if (callback) callback();
|
||||||
|
};
|
||||||
|
script.onerror = function() {
|
||||||
|
console.error('Failed to load script:', src);
|
||||||
|
};
|
||||||
|
document.head.appendChild(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化错误处理
|
||||||
|
function init() {
|
||||||
|
// 修复兼容性问题
|
||||||
|
fixCompatibilityIssues();
|
||||||
|
|
||||||
|
// 检测浏览器支持
|
||||||
|
detectBrowserSupport();
|
||||||
|
|
||||||
|
// 修复具体错误
|
||||||
|
fixSpecificErrors();
|
||||||
|
|
||||||
|
// 为调试添加全局工具
|
||||||
|
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
|
||||||
|
window.debugTools = {
|
||||||
|
safeQuerySelector: safeQuerySelector,
|
||||||
|
safeQuerySelectorAll: safeQuerySelectorAll,
|
||||||
|
safeGetElementById: safeGetElementById,
|
||||||
|
fixCompatibilityIssues: fixCompatibilityIssues,
|
||||||
|
detectBrowserSupport: detectBrowserSupport
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Error handler initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在DOM加载后初始化
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
|
} else {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -18,18 +18,22 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
handleImageErrors();
|
handleImageErrors();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 隐藏页面加载器
|
// 隐藏页面加载器(兼容新的加载器)
|
||||||
function hidePageLoader() {
|
function hidePageLoader() {
|
||||||
const loader = document.getElementById('pageLoader');
|
const loader = document.getElementById('pageLoader') || document.getElementById('page-loader');
|
||||||
if (loader) {
|
if (loader) {
|
||||||
// 添加淡出动画
|
// 如果是新的加载器,使用其自带的隐藏方法
|
||||||
|
if (loader.id === 'page-loader' && window.PageLoader) {
|
||||||
|
window.PageLoader.hideLoader();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 旧的加载器逻辑
|
||||||
loader.style.opacity = '0';
|
loader.style.opacity = '0';
|
||||||
loader.style.transition = 'opacity 0.5s ease-out';
|
loader.style.transition = 'opacity 0.5s ease-out';
|
||||||
|
|
||||||
// 500ms后完全移除
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
loader.style.display = 'none';
|
loader.style.display = 'none';
|
||||||
// 恢复body滚动
|
|
||||||
document.body.style.overflow = 'auto';
|
document.body.style.overflow = 'auto';
|
||||||
document.body.classList.remove('loading');
|
document.body.classList.remove('loading');
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|||||||
516
web_frontend/web_result/js/mobile-optimize.js
Normal file
516
web_frontend/web_result/js/mobile-optimize.js
Normal file
@@ -0,0 +1,516 @@
|
|||||||
|
// 移动端优化组件
|
||||||
|
(function() {
|
||||||
|
let isMobile = false;
|
||||||
|
let isTablet = false;
|
||||||
|
let currentOrientation = 'portrait';
|
||||||
|
|
||||||
|
// 检测设备类型
|
||||||
|
function detectDevice() {
|
||||||
|
const userAgent = navigator.userAgent.toLowerCase();
|
||||||
|
const screenWidth = window.innerWidth;
|
||||||
|
const screenHeight = window.innerHeight;
|
||||||
|
|
||||||
|
// 检测是否为移动设备
|
||||||
|
isMobile = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent) || screenWidth <= 768;
|
||||||
|
isTablet = /ipad|android(?!.*mobile)|tablet/i.test(userAgent) || (screenWidth > 768 && screenWidth <= 1024);
|
||||||
|
|
||||||
|
// 检测屏幕方向
|
||||||
|
currentOrientation = screenWidth > screenHeight ? 'landscape' : 'portrait';
|
||||||
|
|
||||||
|
// 添加设备类到body(确保body存在)
|
||||||
|
if (document.body) {
|
||||||
|
document.body.classList.remove('mobile', 'tablet', 'desktop', 'portrait', 'landscape');
|
||||||
|
document.body.classList.add(
|
||||||
|
isMobile ? 'mobile' : isTablet ? 'tablet' : 'desktop',
|
||||||
|
currentOrientation
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isMobile, isTablet, currentOrientation };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加移动端优化样式
|
||||||
|
function addMobileStyles() {
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = `
|
||||||
|
/* 移动端基础优化 */
|
||||||
|
* {
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 允许文本选择 */
|
||||||
|
p, h1, h2, h3, h4, h5, h6, span, div, article, section {
|
||||||
|
-webkit-user-select: text;
|
||||||
|
-moz-user-select: text;
|
||||||
|
-ms-user-select: text;
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端滚动优化 */
|
||||||
|
html {
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端字体优化 */
|
||||||
|
body.mobile {
|
||||||
|
font-size: 16px; /* 防止iOS缩放 */
|
||||||
|
line-height: 1.6;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-moz-text-size-adjust: 100%;
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端容器优化 */
|
||||||
|
.container {
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mobile .container {
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.tablet .container {
|
||||||
|
padding-left: 2rem;
|
||||||
|
padding-right: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端导航优化 */
|
||||||
|
body.mobile #navbar {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mobile .nav-container {
|
||||||
|
padding: 0.75rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端按钮优化 */
|
||||||
|
body.mobile button,
|
||||||
|
body.mobile .btn {
|
||||||
|
min-height: 44px; /* iOS推荐的最小触摸目标 */
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端表单优化 */
|
||||||
|
body.mobile input,
|
||||||
|
body.mobile textarea,
|
||||||
|
body.mobile select {
|
||||||
|
min-height: 44px;
|
||||||
|
padding: 0.75rem;
|
||||||
|
font-size: 16px; /* 防止iOS缩放 */
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端卡片优化 */
|
||||||
|
body.mobile .card,
|
||||||
|
body.mobile .glass-card {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端文字优化 */
|
||||||
|
body.mobile h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mobile h2 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
line-height: 1.3;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mobile h3 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mobile p {
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端网格优化 */
|
||||||
|
body.mobile .grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mobile .grid-cols-2,
|
||||||
|
body.mobile .grid-cols-3,
|
||||||
|
body.mobile .grid-cols-4,
|
||||||
|
body.mobile .md\\:grid-cols-2,
|
||||||
|
body.mobile .md\\:grid-cols-3,
|
||||||
|
body.mobile .lg\\:grid-cols-3 {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 平板端网格优化 */
|
||||||
|
body.tablet .grid-cols-3,
|
||||||
|
body.tablet .lg\\:grid-cols-3 {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端间距优化 */
|
||||||
|
body.mobile .py-16 { padding-top: 3rem; padding-bottom: 3rem; }
|
||||||
|
body.mobile .py-12 { padding-top: 2rem; padding-bottom: 2rem; }
|
||||||
|
body.mobile .py-8 { padding-top: 1.5rem; padding-bottom: 1.5rem; }
|
||||||
|
body.mobile .mb-12 { margin-bottom: 2rem; }
|
||||||
|
body.mobile .mb-8 { margin-bottom: 1.5rem; }
|
||||||
|
|
||||||
|
/* 移动端图片优化 */
|
||||||
|
body.mobile img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 横屏优化 */
|
||||||
|
body.mobile.landscape {
|
||||||
|
/* 横屏时减少垂直间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mobile.landscape .py-16 {
|
||||||
|
padding-top: 2rem;
|
||||||
|
padding-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mobile.landscape h1 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端返回顶部按钮优化 */
|
||||||
|
body.mobile #back-to-top {
|
||||||
|
bottom: 1.5rem;
|
||||||
|
right: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mobile .back-to-top-btn {
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端页面加载器优化 */
|
||||||
|
body.mobile #page-loader .w-20 {
|
||||||
|
width: 4rem;
|
||||||
|
height: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mobile #page-loader h1 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mobile #page-loader .w-64 {
|
||||||
|
width: 16rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 防止页面缩放 */
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
html {
|
||||||
|
zoom: 1;
|
||||||
|
-ms-zoom: 1;
|
||||||
|
-webkit-zoom: 1;
|
||||||
|
-moz-zoom: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 触摸设备特定优化 */
|
||||||
|
@media (hover: none) and (pointer: coarse) {
|
||||||
|
/* 移除hover效果,使用active效果 */
|
||||||
|
.hover\\:scale-110:hover {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover\\:scale-110:active {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover\\:shadow-xl:hover {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover\\:shadow-xl:active {
|
||||||
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 安全区域适配(iPhone X等带刘海屏的设备) */
|
||||||
|
@supports (padding: max(0px)) {
|
||||||
|
body.mobile {
|
||||||
|
padding-left: max(1rem, env(safe-area-inset-left));
|
||||||
|
padding-right: max(1rem, env(safe-area-inset-right));
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mobile #navbar {
|
||||||
|
padding-left: max(1rem, env(safe-area-inset-left));
|
||||||
|
padding-right: max(1rem, env(safe-area-inset-right));
|
||||||
|
padding-top: max(0.75rem, env(safe-area-inset-top));
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mobile #back-to-top {
|
||||||
|
right: max(1.5rem, env(safe-area-inset-right));
|
||||||
|
bottom: max(1.5rem, env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优化图片加载
|
||||||
|
function optimizeImages() {
|
||||||
|
const images = document.querySelectorAll('img');
|
||||||
|
|
||||||
|
images.forEach(img => {
|
||||||
|
// 添加懒加载
|
||||||
|
if (!img.loading) {
|
||||||
|
img.loading = 'lazy';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加错误处理
|
||||||
|
img.addEventListener('error', function() {
|
||||||
|
this.style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 移动端图片优化
|
||||||
|
if (isMobile) {
|
||||||
|
// 移动端使用较小的图片
|
||||||
|
const src = img.src;
|
||||||
|
if (src && !src.includes('placeholder')) {
|
||||||
|
// 这里可以添加图片压缩逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优化触摸事件
|
||||||
|
function optimizeTouch() {
|
||||||
|
// 添加触摸反馈
|
||||||
|
document.addEventListener('touchstart', function(e) {
|
||||||
|
const target = e.target;
|
||||||
|
if (target.tagName === 'BUTTON' || target.classList.contains('btn') || target.tagName === 'A') {
|
||||||
|
target.style.opacity = '0.7';
|
||||||
|
}
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
document.addEventListener('touchend', function(e) {
|
||||||
|
const target = e.target;
|
||||||
|
if (target.tagName === 'BUTTON' || target.classList.contains('btn') || target.tagName === 'A') {
|
||||||
|
setTimeout(() => {
|
||||||
|
target.style.opacity = '';
|
||||||
|
}, 150);
|
||||||
|
}
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
// 防止双击缩放
|
||||||
|
let lastTouchEnd = 0;
|
||||||
|
document.addEventListener('touchend', function(event) {
|
||||||
|
const now = (new Date()).getTime();
|
||||||
|
if (now - lastTouchEnd <= 300) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
lastTouchEnd = now;
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理屏幕方向变化
|
||||||
|
function handleOrientationChange() {
|
||||||
|
// 延迟执行以确保尺寸更新
|
||||||
|
setTimeout(() => {
|
||||||
|
detectDevice();
|
||||||
|
|
||||||
|
// 重新计算布局
|
||||||
|
const event = new Event('resize');
|
||||||
|
window.dispatchEvent(event);
|
||||||
|
|
||||||
|
// 滚动到顶部(防止方向变化后位置错乱)
|
||||||
|
if (window.scrollY > 100) {
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优化键盘弹出时的布局
|
||||||
|
function handleKeyboard() {
|
||||||
|
if (!isMobile) return;
|
||||||
|
|
||||||
|
const originalHeight = window.innerHeight;
|
||||||
|
let keyboardOpen = false;
|
||||||
|
|
||||||
|
window.addEventListener('resize', function() {
|
||||||
|
const currentHeight = window.innerHeight;
|
||||||
|
const heightDifference = originalHeight - currentHeight;
|
||||||
|
|
||||||
|
// 键盘弹出(高度减少超过150px)
|
||||||
|
if (heightDifference > 150 && !keyboardOpen) {
|
||||||
|
keyboardOpen = true;
|
||||||
|
document.body.classList.add('keyboard-open');
|
||||||
|
|
||||||
|
// 隐藏导航栏(为输入框腾出空间)
|
||||||
|
const navbar = document.getElementById('navbar');
|
||||||
|
if (navbar) {
|
||||||
|
navbar.style.transform = 'translateY(-100%)';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 隐藏返回顶部按钮
|
||||||
|
const backToTop = document.getElementById('back-to-top');
|
||||||
|
if (backToTop) {
|
||||||
|
backToTop.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 键盘收起
|
||||||
|
else if (heightDifference <= 150 && keyboardOpen) {
|
||||||
|
keyboardOpen = false;
|
||||||
|
document.body.classList.remove('keyboard-open');
|
||||||
|
|
||||||
|
// 恢复导航栏
|
||||||
|
const navbar = document.getElementById('navbar');
|
||||||
|
if (navbar) {
|
||||||
|
navbar.style.transform = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 恢复返回顶部按钮
|
||||||
|
const backToTop = document.getElementById('back-to-top');
|
||||||
|
if (backToTop) {
|
||||||
|
backToTop.style.display = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 性能优化
|
||||||
|
function optimizePerformance() {
|
||||||
|
// 使用 Intersection Observer 优化动画
|
||||||
|
if ('IntersectionObserver' in window) {
|
||||||
|
const observer = new IntersectionObserver((entries) => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
entry.target.classList.add('animate-visible');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, {
|
||||||
|
threshold: 0.1,
|
||||||
|
rootMargin: '50px'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 观察所有动画元素
|
||||||
|
document.querySelectorAll('[class*="animate-"]').forEach(el => {
|
||||||
|
if (!el.classList.contains('animate-visible')) {
|
||||||
|
observer.observe(el);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移动端减少动画
|
||||||
|
if (isMobile) {
|
||||||
|
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
|
||||||
|
if (mediaQuery.matches) {
|
||||||
|
document.body.classList.add('reduce-motion');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加调试信息(开发环境)
|
||||||
|
function addDebugInfo() {
|
||||||
|
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
|
||||||
|
const debugInfo = document.createElement('div');
|
||||||
|
debugInfo.style.cssText = `
|
||||||
|
position: fixed;
|
||||||
|
top: 70px;
|
||||||
|
right: 10px;
|
||||||
|
background: rgba(0,0,0,0.8);
|
||||||
|
color: white;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
z-index: 10000;
|
||||||
|
font-family: monospace;
|
||||||
|
`;
|
||||||
|
|
||||||
|
function updateDebugInfo() {
|
||||||
|
const { isMobile, isTablet, currentOrientation } = detectDevice();
|
||||||
|
debugInfo.innerHTML = `
|
||||||
|
${window.innerWidth}×${window.innerHeight}<br>
|
||||||
|
${isMobile ? 'Mobile' : isTablet ? 'Tablet' : 'Desktop'}<br>
|
||||||
|
${currentOrientation}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDebugInfo();
|
||||||
|
document.body.appendChild(debugInfo);
|
||||||
|
|
||||||
|
window.addEventListener('resize', updateDebugInfo);
|
||||||
|
window.addEventListener('orientationchange', updateDebugInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化移动端优化
|
||||||
|
function init() {
|
||||||
|
// 检测设备类型
|
||||||
|
detectDevice();
|
||||||
|
|
||||||
|
// 添加移动端样式
|
||||||
|
addMobileStyles();
|
||||||
|
|
||||||
|
// 优化触摸体验
|
||||||
|
optimizeTouch();
|
||||||
|
|
||||||
|
// 处理屏幕方向变化
|
||||||
|
window.addEventListener('orientationchange', handleOrientationChange);
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
setTimeout(detectDevice, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 页面加载完成后的优化
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
optimizeImages();
|
||||||
|
handleKeyboard();
|
||||||
|
optimizePerformance();
|
||||||
|
addDebugInfo();
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setTimeout(() => {
|
||||||
|
optimizeImages();
|
||||||
|
handleKeyboard();
|
||||||
|
optimizePerformance();
|
||||||
|
addDebugInfo();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听页面加载完成事件
|
||||||
|
document.addEventListener('pageLoaded', () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
optimizeImages();
|
||||||
|
optimizePerformance();
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提供外部接口
|
||||||
|
window.MobileOptimize = {
|
||||||
|
detectDevice,
|
||||||
|
isMobile: () => isMobile,
|
||||||
|
isTablet: () => isTablet,
|
||||||
|
getCurrentOrientation: () => currentOrientation,
|
||||||
|
optimizeImages,
|
||||||
|
optimizePerformance
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
init();
|
||||||
|
})();
|
||||||
@@ -10,8 +10,8 @@
|
|||||||
{ href: 'index.html', icon: 'fa-home', text: '首页', id: 'index' },
|
{ href: 'index.html', icon: 'fa-home', text: '首页', id: 'index' },
|
||||||
{ href: 'overview.html', icon: 'fa-info-circle', text: '展会概览', id: 'overview' },
|
{ href: 'overview.html', icon: 'fa-info-circle', text: '展会概览', id: 'overview' },
|
||||||
{ href: 'exhibition.html', icon: 'fa-th-large', text: '展览内容', id: 'exhibition' },
|
{ href: 'exhibition.html', icon: 'fa-th-large', text: '展览内容', id: 'exhibition' },
|
||||||
|
{ href: 'operation.html', icon: 'fa-drafting-compass', text: '布局设计', id: 'operation' },
|
||||||
{ href: 'marketing.html', icon: 'fa-bullhorn', text: '营销推广', id: 'marketing' },
|
{ href: 'marketing.html', icon: 'fa-bullhorn', text: '营销推广', id: 'marketing' },
|
||||||
{ href: 'operation.html', icon: 'fa-cogs', text: '运营服务', id: 'operation' },
|
|
||||||
{ href: 'budget.html', icon: 'fa-chart-pie', text: '预算分析', id: 'budget' },
|
{ href: 'budget.html', icon: 'fa-chart-pie', text: '预算分析', id: 'budget' },
|
||||||
{ href: 'risk.html', icon: 'fa-shield-alt', text: '风险评估', id: 'risk' }
|
{ href: 'risk.html', icon: 'fa-shield-alt', text: '风险评估', id: 'risk' }
|
||||||
];
|
];
|
||||||
@@ -34,82 +34,230 @@
|
|||||||
// 生成导航HTML
|
// 生成导航HTML
|
||||||
function generateNavHTML() {
|
function generateNavHTML() {
|
||||||
return `
|
return `
|
||||||
<div class="container mx-auto px-6 py-4">
|
<div class="nav-container container mx-auto px-6 py-4">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex items-center fade-in-left">
|
<div class="flex items-center nav-logo">
|
||||||
<div class="w-10 h-10 bg-gradient-to-br from-emerald-400 to-blue-500 rounded-lg flex items-center justify-center mr-3">
|
<div class="logo-icon w-10 h-10 bg-gradient-to-br from-emerald-400 to-blue-500 rounded-lg flex items-center justify-center mr-3 transition-transform hover:scale-110">
|
||||||
<i class="fas fa-charging-station text-white"></i>
|
<i class="fas fa-charging-station text-white"></i>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-lg font-bold">NEVIT 2024</h1>
|
<h1 class="text-lg font-bold text-gray-800">NEVIT 2024</h1>
|
||||||
<p class="text-xs text-gray-500">新能源汽车产业博览会</p>
|
<p class="text-xs text-gray-500">新能源汽车产业博览会</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden md:flex space-x-8">
|
<div class="hidden md:flex space-x-6 lg:space-x-8">
|
||||||
${navItems.map(item => `
|
${navItems.map(item => `
|
||||||
<a href="${getCorrectPath(item.href)}"
|
<a href="${getCorrectPath(item.href)}"
|
||||||
class="nav-link ${isActive(item.href) ? 'active' : ''} text-gray-700 hover:text-emerald-500 transition-colors"
|
class="nav-link ${isActive(item.href) ? 'active text-emerald-500' : 'text-gray-700'} hover:text-emerald-500 transition-all duration-300 relative group"
|
||||||
data-nav="${item.id}">
|
data-nav="${item.id}">
|
||||||
<i class="fas ${item.icon} mr-2"></i>${item.text}
|
<i class="fas ${item.icon} mr-2"></i>${item.text}
|
||||||
|
${isActive(item.href) ? '<span class="absolute bottom-0 left-0 w-full h-0.5 bg-emerald-500"></span>' : '<span class="absolute bottom-0 left-0 w-0 h-0.5 bg-emerald-500 transition-all duration-300 group-hover:w-full"></span>'}
|
||||||
</a>
|
</a>
|
||||||
`).join('')}
|
`).join('')}
|
||||||
</div>
|
</div>
|
||||||
<button class="md:hidden" id="mobile-menu-button">
|
<button class="md:hidden menu-toggle" id="mobile-menu-button">
|
||||||
<i class="fas fa-bars text-2xl text-gray-700"></i>
|
<span class="menu-icon">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mobile Menu -->
|
<!-- Mobile Menu -->
|
||||||
<div class="hidden md:hidden fixed top-16 left-0 w-full bg-white shadow-lg" id="mobile-menu">
|
<div class="mobile-menu-overlay hidden" id="mobile-menu-overlay"></div>
|
||||||
<div class="px-6 py-4 space-y-3">
|
<div class="mobile-menu transform translate-x-full transition-transform duration-300 fixed top-0 right-0 h-full w-80 bg-white shadow-2xl z-50" id="mobile-menu">
|
||||||
${navItems.map(item => `
|
<div class="p-6">
|
||||||
<a href="${getCorrectPath(item.href)}"
|
<button class="absolute top-6 right-6 text-gray-500 hover:text-gray-700" id="close-menu">
|
||||||
class="block py-2 px-4 rounded ${isActive(item.href) ? 'bg-emerald-50 text-emerald-500' : 'text-gray-700 hover:bg-gray-50'}"
|
<i class="fas fa-times text-2xl"></i>
|
||||||
data-nav="${item.id}">
|
</button>
|
||||||
<i class="fas ${item.icon} mr-2"></i>${item.text}
|
<div class="mb-8">
|
||||||
</a>
|
<h2 class="text-2xl font-bold text-gray-800">NEVIT 2024</h2>
|
||||||
`).join('')}
|
<p class="text-sm text-gray-500">新能源汽车产业博览会</p>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-4">
|
||||||
|
${navItems.map(item => `
|
||||||
|
<a href="${getCorrectPath(item.href)}"
|
||||||
|
class="block py-3 px-4 rounded-lg ${isActive(item.href) ? 'bg-emerald-50 text-emerald-500' : 'text-gray-700 hover:bg-gray-50'} transition-all duration-300"
|
||||||
|
data-nav="${item.id}">
|
||||||
|
<i class="fas ${item.icon} mr-3 w-5"></i>${item.text}
|
||||||
|
</a>
|
||||||
|
`).join('')}
|
||||||
|
</div>
|
||||||
|
<div class="mt-8 pt-8 border-t border-gray-200">
|
||||||
|
<p class="text-sm text-gray-500">2024年9月12-15日</p>
|
||||||
|
<p class="text-sm text-gray-500">国家会展中心(上海)</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加样式
|
||||||
|
function addStyles() {
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = `
|
||||||
|
.nav-container {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
.navbar-scrolled {
|
||||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.navbar-scrolled .nav-container {
|
||||||
|
padding-top: 0.75rem;
|
||||||
|
padding-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
.nav-logo {
|
||||||
|
animation: fadeInLeft 0.6s ease-out;
|
||||||
|
}
|
||||||
|
.nav-link {
|
||||||
|
position: relative;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.menu-icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 20px;
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.menu-icon span {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background-color: #374151;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
.menu-icon span:nth-child(1) {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.menu-icon span:nth-child(2) {
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
.menu-icon span:nth-child(3) {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
.menu-open .menu-icon span:nth-child(1) {
|
||||||
|
transform: rotate(45deg) translate(6px, 6px);
|
||||||
|
}
|
||||||
|
.menu-open .menu-icon span:nth-child(2) {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.menu-open .menu-icon span:nth-child(3) {
|
||||||
|
transform: rotate(-45deg) translate(6px, -6px);
|
||||||
|
}
|
||||||
|
.mobile-menu-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 40;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
.mobile-menu-overlay.show {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.mobile-menu.show {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
@keyframes fadeInLeft {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.nav-container {
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化导航
|
// 初始化导航
|
||||||
function initNav() {
|
function initNav() {
|
||||||
const navElement = document.querySelector('nav') || document.getElementById('navbar');
|
const navElement = document.querySelector('nav') || document.getElementById('navbar');
|
||||||
if (navElement) {
|
if (navElement) {
|
||||||
// 统一设置导航栏样式 - 确保所有页面一致
|
// 统一设置导航栏样式
|
||||||
navElement.className = 'fixed top-0 w-full bg-white shadow-md z-50 transition-all duration-300';
|
navElement.className = 'fixed top-0 w-full bg-white z-50 transition-all duration-300';
|
||||||
navElement.id = 'navbar';
|
navElement.id = 'navbar';
|
||||||
navElement.style.backgroundColor = 'white';
|
|
||||||
navElement.style.zIndex = '9999';
|
// 添加样式
|
||||||
|
addStyles();
|
||||||
|
|
||||||
// 更新导航内容
|
// 更新导航内容
|
||||||
navElement.innerHTML = generateNavHTML();
|
navElement.innerHTML = generateNavHTML();
|
||||||
|
|
||||||
// 添加移动端菜单交互
|
// 添加滚动效果
|
||||||
|
let lastScrollTop = 0;
|
||||||
|
window.addEventListener('scroll', function() {
|
||||||
|
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
||||||
|
|
||||||
|
// 添加滚动阴影
|
||||||
|
if (scrollTop > 50) {
|
||||||
|
navElement.classList.add('navbar-scrolled');
|
||||||
|
} else {
|
||||||
|
navElement.classList.remove('navbar-scrolled');
|
||||||
|
}
|
||||||
|
|
||||||
|
lastScrollTop = scrollTop;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 移动端菜单交互
|
||||||
const mobileMenuButton = document.getElementById('mobile-menu-button');
|
const mobileMenuButton = document.getElementById('mobile-menu-button');
|
||||||
const mobileMenu = document.getElementById('mobile-menu');
|
const mobileMenu = document.getElementById('mobile-menu');
|
||||||
|
const mobileMenuOverlay = document.getElementById('mobile-menu-overlay');
|
||||||
|
const closeMenuButton = document.getElementById('close-menu');
|
||||||
|
|
||||||
if (mobileMenuButton && mobileMenu) {
|
if (mobileMenuButton && mobileMenu) {
|
||||||
mobileMenuButton.addEventListener('click', () => {
|
// 打开菜单
|
||||||
mobileMenu.classList.toggle('hidden');
|
mobileMenuButton.addEventListener('click', function() {
|
||||||
|
mobileMenu.classList.add('show');
|
||||||
|
if (mobileMenuOverlay) {
|
||||||
|
mobileMenuOverlay.classList.remove('hidden');
|
||||||
|
setTimeout(function() {
|
||||||
|
mobileMenuOverlay.classList.add('show');
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
mobileMenuButton.classList.add('menu-open');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
});
|
});
|
||||||
|
|
||||||
// 点击菜单项后关闭移动端菜单
|
// 关闭菜单功能
|
||||||
mobileMenu.querySelectorAll('a').forEach(link => {
|
const closeMenu = function() {
|
||||||
link.addEventListener('click', () => {
|
mobileMenu.classList.remove('show');
|
||||||
mobileMenu.classList.add('hidden');
|
if (mobileMenuOverlay) {
|
||||||
});
|
mobileMenuOverlay.classList.remove('show');
|
||||||
|
setTimeout(function() {
|
||||||
|
mobileMenuOverlay.classList.add('hidden');
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
mobileMenuButton.classList.remove('menu-open');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 点击关闭按钮
|
||||||
|
closeMenuButton.addEventListener('click', closeMenu);
|
||||||
|
|
||||||
|
// 点击遮罩关闭
|
||||||
|
mobileMenuOverlay.addEventListener('click', closeMenu);
|
||||||
|
|
||||||
|
// 点击菜单项后关闭
|
||||||
|
mobileMenu.querySelectorAll('a').forEach(function(link) {
|
||||||
|
link.addEventListener('click', closeMenu);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保持导航栏始终可见,不添加滚动隐藏效果
|
|
||||||
// 确保导航栏始终在最顶层
|
|
||||||
navElement.style.position = 'fixed';
|
|
||||||
navElement.style.top = '0';
|
|
||||||
navElement.style.left = '0';
|
|
||||||
navElement.style.right = '0';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
387
web_frontend/web_result/js/page-loader.js
Normal file
387
web_frontend/web_result/js/page-loader.js
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
// 页面加载动画组件
|
||||||
|
(function() {
|
||||||
|
// 创建加载器HTML结构
|
||||||
|
function createLoader() {
|
||||||
|
const loaderHTML = `
|
||||||
|
<div id="page-loader" class="fixed inset-0 bg-white z-[9999] flex items-center justify-center">
|
||||||
|
<!-- 背景动画 -->
|
||||||
|
<div class="absolute inset-0 overflow-hidden">
|
||||||
|
<!-- 渐变背景 -->
|
||||||
|
<div class="absolute inset-0 bg-gradient-to-br from-blue-50 via-purple-50 to-emerald-50"></div>
|
||||||
|
|
||||||
|
<!-- 浮动粒子 -->
|
||||||
|
<div class="floating-particles">
|
||||||
|
${Array.from({length: 12}).map((_, i) => `
|
||||||
|
<div class="particle particle-${i + 1}"></div>
|
||||||
|
`).join('')}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 几何图形 -->
|
||||||
|
<div class="geometric-shapes">
|
||||||
|
<div class="shape shape-1"></div>
|
||||||
|
<div class="shape shape-2"></div>
|
||||||
|
<div class="shape shape-3"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 主加载内容 -->
|
||||||
|
<div class="relative z-10 text-center">
|
||||||
|
<!-- Logo区域 -->
|
||||||
|
<div class="mb-8">
|
||||||
|
<div class="w-20 h-20 mx-auto mb-4 relative">
|
||||||
|
<!-- 旋转的环 -->
|
||||||
|
<div class="absolute inset-0 border-4 border-transparent border-t-emerald-500 border-r-blue-500 rounded-full animate-spin"></div>
|
||||||
|
<div class="absolute inset-2 border-4 border-transparent border-b-purple-500 border-l-pink-500 rounded-full animate-spin-reverse"></div>
|
||||||
|
|
||||||
|
<!-- 中心图标 -->
|
||||||
|
<div class="absolute inset-0 flex items-center justify-center">
|
||||||
|
<div class="w-8 h-8 bg-gradient-to-br from-emerald-400 to-blue-500 rounded-lg flex items-center justify-center text-white font-bold text-sm animate-pulse">
|
||||||
|
新
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 品牌名称 -->
|
||||||
|
<h1 class="text-2xl font-bold text-gray-800 mb-2 animate-fade-in">
|
||||||
|
长三角国际新能源汽车博览会
|
||||||
|
</h1>
|
||||||
|
<p class="text-gray-600 animate-fade-in-delay">
|
||||||
|
智行未来,绿动长三角
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 进度条 -->
|
||||||
|
<div class="w-64 mx-auto mb-6">
|
||||||
|
<div class="bg-gray-200 rounded-full h-2 overflow-hidden">
|
||||||
|
<div id="loading-progress" class="h-full bg-gradient-to-r from-emerald-400 to-blue-500 rounded-full transition-all duration-300 ease-out" style="width: 0%"></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between text-xs text-gray-500 mt-2">
|
||||||
|
<span>加载中</span>
|
||||||
|
<span id="loading-percentage">0%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 加载状态文字 -->
|
||||||
|
<div class="text-sm text-gray-500">
|
||||||
|
<span id="loading-text">正在准备展会信息</span>
|
||||||
|
<span class="loading-dots">
|
||||||
|
<span class="dot">.</span>
|
||||||
|
<span class="dot">.</span>
|
||||||
|
<span class="dot">.</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 将加载器插入到 body 的最前面,确保body存在
|
||||||
|
if (document.body) {
|
||||||
|
document.body.insertAdjacentHTML('afterbegin', loaderHTML);
|
||||||
|
} else {
|
||||||
|
// 如果body不存在,等待DOM加载完成
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
if (document.body && !document.getElementById('page-loader')) {
|
||||||
|
document.body.insertAdjacentHTML('afterbegin', loaderHTML);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加样式
|
||||||
|
function addStyles() {
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = `
|
||||||
|
/* 基础动画定义 */
|
||||||
|
@keyframes spin-reverse {
|
||||||
|
from { transform: rotate(0deg); }
|
||||||
|
to { transform: rotate(-360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in {
|
||||||
|
from { opacity: 0; transform: translateY(20px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in-delay {
|
||||||
|
from { opacity: 0; transform: translateY(15px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float {
|
||||||
|
0%, 100% { transform: translateY(0px) rotate(0deg); }
|
||||||
|
25% { transform: translateY(-10px) rotate(90deg); }
|
||||||
|
50% { transform: translateY(-5px) rotate(180deg); }
|
||||||
|
75% { transform: translateY(-15px) rotate(270deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dot-blink {
|
||||||
|
0%, 20% { opacity: 0; }
|
||||||
|
50% { opacity: 1; }
|
||||||
|
100% { opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shape-float {
|
||||||
|
0%, 100% { transform: translateY(0px) translateX(0px) rotate(0deg); }
|
||||||
|
33% { transform: translateY(-20px) translateX(10px) rotate(120deg); }
|
||||||
|
66% { transform: translateY(10px) translateX(-10px) rotate(240deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes particle-drift {
|
||||||
|
0% { transform: translateY(100vh) translateX(0px) rotate(0deg); opacity: 0; }
|
||||||
|
10% { opacity: 1; }
|
||||||
|
90% { opacity: 1; }
|
||||||
|
100% { transform: translateY(-100px) translateX(50px) rotate(360deg); opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 应用动画类 */
|
||||||
|
.animate-spin-reverse {
|
||||||
|
animation: spin-reverse 2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-in {
|
||||||
|
animation: fade-in 1s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-in-delay {
|
||||||
|
animation: fade-in-delay 1s ease-out 0.3s forwards;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加载点动画 */
|
||||||
|
.loading-dots {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-dots .dot {
|
||||||
|
animation: dot-blink 1.4s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-dots .dot:nth-child(2) {
|
||||||
|
animation-delay: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-dots .dot:nth-child(3) {
|
||||||
|
animation-delay: 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 浮动粒子 */
|
||||||
|
.floating-particles {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.particle {
|
||||||
|
position: absolute;
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: particle-drift 8s infinite linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.particle-1 { background: rgba(16, 185, 129, 0.6); left: 10%; animation-delay: 0s; animation-duration: 8s; }
|
||||||
|
.particle-2 { background: rgba(59, 130, 246, 0.6); left: 20%; animation-delay: 1s; animation-duration: 10s; }
|
||||||
|
.particle-3 { background: rgba(168, 85, 247, 0.6); left: 30%; animation-delay: 2s; animation-duration: 9s; }
|
||||||
|
.particle-4 { background: rgba(236, 72, 153, 0.6); left: 40%; animation-delay: 3s; animation-duration: 11s; }
|
||||||
|
.particle-5 { background: rgba(245, 158, 11, 0.6); left: 50%; animation-delay: 4s; animation-duration: 7s; }
|
||||||
|
.particle-6 { background: rgba(239, 68, 68, 0.6); left: 60%; animation-delay: 5s; animation-duration: 9s; }
|
||||||
|
.particle-7 { background: rgba(16, 185, 129, 0.6); left: 70%; animation-delay: 6s; animation-duration: 8s; }
|
||||||
|
.particle-8 { background: rgba(59, 130, 246, 0.6); left: 80%; animation-delay: 7s; animation-duration: 10s; }
|
||||||
|
.particle-9 { background: rgba(168, 85, 247, 0.6); left: 90%; animation-delay: 1.5s; animation-duration: 9s; }
|
||||||
|
.particle-10 { background: rgba(236, 72, 153, 0.6); left: 15%; animation-delay: 2.5s; animation-duration: 11s; }
|
||||||
|
.particle-11 { background: rgba(245, 158, 11, 0.6); left: 25%; animation-delay: 3.5s; animation-duration: 7s; }
|
||||||
|
.particle-12 { background: rgba(239, 68, 68, 0.6); left: 85%; animation-delay: 4.5s; animation-duration: 8s; }
|
||||||
|
|
||||||
|
/* 几何图形 */
|
||||||
|
.geometric-shapes {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shape {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 12px;
|
||||||
|
animation: shape-float 6s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shape-1 {
|
||||||
|
top: 20%;
|
||||||
|
left: 10%;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background: linear-gradient(45deg, rgba(16, 185, 129, 0.1), rgba(59, 130, 246, 0.1));
|
||||||
|
animation-delay: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shape-2 {
|
||||||
|
top: 60%;
|
||||||
|
right: 15%;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
background: linear-gradient(135deg, rgba(168, 85, 247, 0.1), rgba(236, 72, 153, 0.1));
|
||||||
|
border-radius: 50%;
|
||||||
|
animation-delay: 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shape-3 {
|
||||||
|
bottom: 30%;
|
||||||
|
left: 20%;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background: linear-gradient(225deg, rgba(245, 158, 11, 0.1), rgba(239, 68, 68, 0.1));
|
||||||
|
animation-delay: 4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加载器隐藏动画 */
|
||||||
|
#page-loader.fade-out {
|
||||||
|
animation: fadeOutLoader 0.8s ease-in-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeOutLoader {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 防止页面滚动 */
|
||||||
|
body.loading {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新加载进度
|
||||||
|
function updateProgress(percentage, text) {
|
||||||
|
const progressBar = document.getElementById('loading-progress');
|
||||||
|
const percentageText = document.getElementById('loading-percentage');
|
||||||
|
const loadingText = document.getElementById('loading-text');
|
||||||
|
|
||||||
|
if (progressBar) {
|
||||||
|
progressBar.style.width = percentage + '%';
|
||||||
|
}
|
||||||
|
if (percentageText) {
|
||||||
|
percentageText.textContent = percentage + '%';
|
||||||
|
}
|
||||||
|
if (loadingText && text) {
|
||||||
|
loadingText.textContent = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟加载过程
|
||||||
|
function simulateLoading() {
|
||||||
|
const loadingSteps = [
|
||||||
|
{ progress: 0, text: '正在准备展会信息', delay: 100 },
|
||||||
|
{ progress: 15, text: '加载导航组件', delay: 200 },
|
||||||
|
{ progress: 30, text: '获取展会数据', delay: 300 },
|
||||||
|
{ progress: 45, text: '渲染页面布局', delay: 250 },
|
||||||
|
{ progress: 60, text: '加载图片资源', delay: 400 },
|
||||||
|
{ progress: 75, text: '初始化交互功能', delay: 200 },
|
||||||
|
{ progress: 85, text: '优化页面性能', delay: 150 },
|
||||||
|
{ progress: 95, text: '准备完成', delay: 200 },
|
||||||
|
{ progress: 100, text: '加载完成', delay: 300 }
|
||||||
|
];
|
||||||
|
|
||||||
|
let currentStep = 0;
|
||||||
|
|
||||||
|
function nextStep() {
|
||||||
|
if (currentStep < loadingSteps.length) {
|
||||||
|
const step = loadingSteps[currentStep];
|
||||||
|
updateProgress(step.progress, step.text);
|
||||||
|
currentStep++;
|
||||||
|
setTimeout(nextStep, step.delay);
|
||||||
|
} else {
|
||||||
|
// 加载完成,延迟一点时间后隐藏加载器
|
||||||
|
setTimeout(hideLoader, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始加载过程
|
||||||
|
setTimeout(nextStep, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 隐藏加载器
|
||||||
|
function hideLoader() {
|
||||||
|
const loader = document.getElementById('page-loader');
|
||||||
|
if (loader) {
|
||||||
|
loader.classList.add('fade-out');
|
||||||
|
|
||||||
|
// 动画结束后移除元素并恢复页面滚动
|
||||||
|
setTimeout(() => {
|
||||||
|
loader.remove();
|
||||||
|
document.body.classList.remove('loading');
|
||||||
|
|
||||||
|
// 触发页面加载完成事件
|
||||||
|
document.dispatchEvent(new CustomEvent('pageLoaded'));
|
||||||
|
}, 800);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化加载器
|
||||||
|
function init() {
|
||||||
|
// 确保DOM已加载
|
||||||
|
if (!document.body) {
|
||||||
|
// 如果body还不存在,等待DOM加载完成
|
||||||
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加样式
|
||||||
|
addStyles();
|
||||||
|
|
||||||
|
// 创建加载器
|
||||||
|
createLoader();
|
||||||
|
|
||||||
|
// 禁用页面滚动
|
||||||
|
if (document.body) {
|
||||||
|
document.body.classList.add('loading');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待页面资源加载完成
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
setTimeout(simulateLoading, 200);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setTimeout(simulateLoading, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听 window load 事件(所有资源包括图片都加载完成)
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
// 如果加载器仍然存在,加速完成过程
|
||||||
|
if (document.getElementById('page-loader')) {
|
||||||
|
updateProgress(100, '加载完成');
|
||||||
|
setTimeout(hideLoader, 300);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听页面可见性变化(用户切换标签页时暂停动画)
|
||||||
|
document.addEventListener('visibilitychange', function() {
|
||||||
|
const loader = document.getElementById('page-loader');
|
||||||
|
if (loader) {
|
||||||
|
if (document.hidden) {
|
||||||
|
loader.style.animationPlayState = 'paused';
|
||||||
|
} else {
|
||||||
|
loader.style.animationPlayState = 'running';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提供外部接口
|
||||||
|
window.PageLoader = {
|
||||||
|
updateProgress,
|
||||||
|
hideLoader
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
init();
|
||||||
|
})();
|
||||||
461
web_frontend/web_result/js/performance-optimizer.js
Normal file
461
web_frontend/web_result/js/performance-optimizer.js
Normal file
@@ -0,0 +1,461 @@
|
|||||||
|
// 页面性能优化组件
|
||||||
|
(function() {
|
||||||
|
let performanceData = {};
|
||||||
|
let isOptimizing = false;
|
||||||
|
|
||||||
|
// 性能监测
|
||||||
|
function measurePerformance() {
|
||||||
|
if ('performance' in window) {
|
||||||
|
const navigation = performance.getEntriesByType('navigation')[0];
|
||||||
|
const paint = performance.getEntriesByType('paint');
|
||||||
|
|
||||||
|
performanceData = {
|
||||||
|
// 页面加载时间
|
||||||
|
domLoading: navigation.domContentLoadedEventEnd - navigation.navigationStart,
|
||||||
|
pageLoading: navigation.loadEventEnd - navigation.navigationStart,
|
||||||
|
|
||||||
|
// 渲染时间
|
||||||
|
firstPaint: paint.find(p => p.name === 'first-paint')?.startTime || 0,
|
||||||
|
firstContentfulPaint: paint.find(p => p.name === 'first-contentful-paint')?.startTime || 0,
|
||||||
|
|
||||||
|
// 网络时间
|
||||||
|
dnsTime: navigation.domainLookupEnd - navigation.domainLookupStart,
|
||||||
|
connectTime: navigation.connectEnd - navigation.connectStart,
|
||||||
|
requestTime: navigation.responseStart - navigation.requestStart,
|
||||||
|
downloadTime: navigation.responseEnd - navigation.responseStart,
|
||||||
|
|
||||||
|
// 内存使用(如果支持)
|
||||||
|
memory: performance.memory ? {
|
||||||
|
used: Math.round(performance.memory.usedJSHeapSize / 1024 / 1024),
|
||||||
|
total: Math.round(performance.memory.totalJSHeapSize / 1024 / 1024),
|
||||||
|
limit: Math.round(performance.memory.jsHeapSizeLimit / 1024 / 1024)
|
||||||
|
} : null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return performanceData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 资源预加载优化
|
||||||
|
function optimizeResourceLoading() {
|
||||||
|
// 根据当前页面位置调整路径
|
||||||
|
const isInPagesFolder = window.location.pathname.includes('/pages/');
|
||||||
|
const basePath = isInPagesFolder ? '../' : '';
|
||||||
|
|
||||||
|
// 预加载关键资源
|
||||||
|
const criticalResources = [
|
||||||
|
{ href: basePath + 'css/styles.css', as: 'style' },
|
||||||
|
{ href: basePath + 'css/animations.css', as: 'style' },
|
||||||
|
{ href: basePath + 'js/nav-component.js', as: 'script' }
|
||||||
|
];
|
||||||
|
|
||||||
|
criticalResources.forEach(resource => {
|
||||||
|
if (!document.querySelector(`link[href*="${resource.href.split('/').pop()}"]`)) {
|
||||||
|
const link = document.createElement('link');
|
||||||
|
link.rel = 'preload';
|
||||||
|
link.href = resource.href;
|
||||||
|
link.as = resource.as;
|
||||||
|
document.head.appendChild(link);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片懒加载和优化
|
||||||
|
function optimizeImages() {
|
||||||
|
// 创建 Intersection Observer 用于懒加载
|
||||||
|
if ('IntersectionObserver' in window) {
|
||||||
|
const imageObserver = new IntersectionObserver((entries, observer) => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
const img = entry.target;
|
||||||
|
|
||||||
|
// 懒加载图片
|
||||||
|
if (img.dataset.src) {
|
||||||
|
img.src = img.dataset.src;
|
||||||
|
img.removeAttribute('data-src');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加加载状态
|
||||||
|
img.classList.add('loading');
|
||||||
|
|
||||||
|
img.addEventListener('load', () => {
|
||||||
|
img.classList.remove('loading');
|
||||||
|
img.classList.add('loaded');
|
||||||
|
});
|
||||||
|
|
||||||
|
img.addEventListener('error', () => {
|
||||||
|
img.classList.add('error');
|
||||||
|
img.style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.unobserve(img);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, {
|
||||||
|
rootMargin: '50px',
|
||||||
|
threshold: 0.01
|
||||||
|
});
|
||||||
|
|
||||||
|
// 观察所有图片
|
||||||
|
document.querySelectorAll('img[data-src], img:not([src])').forEach(img => {
|
||||||
|
imageObserver.observe(img);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为现有图片添加优化
|
||||||
|
document.querySelectorAll('img').forEach(img => {
|
||||||
|
// 添加 loading="lazy" 属性
|
||||||
|
if (!img.loading) {
|
||||||
|
img.loading = 'lazy';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加 decode="async" 属性
|
||||||
|
if (!img.decode) {
|
||||||
|
img.decode = 'async';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片错误处理
|
||||||
|
img.addEventListener('error', function() {
|
||||||
|
this.style.opacity = '0';
|
||||||
|
this.style.transition = 'opacity 0.3s';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSS 优化
|
||||||
|
function optimizeCSS() {
|
||||||
|
// 内联关键 CSS
|
||||||
|
const criticalCSS = `
|
||||||
|
/* 关键渲染路径 CSS */
|
||||||
|
body { font-family: 'Inter', sans-serif; margin: 0; padding: 0; }
|
||||||
|
#navbar { position: fixed; top: 0; width: 100%; z-index: 50; background: white; }
|
||||||
|
.container { max-width: 1200px; margin: 0 auto; padding: 0 1rem; }
|
||||||
|
|
||||||
|
/* 页面加载器样式 */
|
||||||
|
#page-loader { position: fixed; inset: 0; z-index: 9999; background: white; display: flex; align-items: center; justify-content: center; }
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 添加关键 CSS 到 head
|
||||||
|
if (!document.getElementById('critical-css')) {
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.id = 'critical-css';
|
||||||
|
style.textContent = criticalCSS;
|
||||||
|
document.head.insertBefore(style, document.head.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 异步加载非关键 CSS
|
||||||
|
const nonCriticalCSS = document.querySelectorAll('link[rel="stylesheet"]:not([data-critical])');
|
||||||
|
nonCriticalCSS.forEach(link => {
|
||||||
|
if (link.href.includes('tailwind') || link.href.includes('font-awesome')) {
|
||||||
|
link.media = 'print';
|
||||||
|
link.addEventListener('load', function() {
|
||||||
|
this.media = 'all';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// JavaScript 优化
|
||||||
|
function optimizeJavaScript() {
|
||||||
|
// 延迟加载非关键脚本
|
||||||
|
const deferScripts = [
|
||||||
|
'js/back-to-top.js',
|
||||||
|
'js/mobile-optimize.js'
|
||||||
|
];
|
||||||
|
|
||||||
|
deferScripts.forEach(src => {
|
||||||
|
const script = document.querySelector(`script[src*="${src}"]`);
|
||||||
|
if (script && !script.defer && !script.async) {
|
||||||
|
script.defer = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 移除未使用的事件监听器(防止内存泄漏)
|
||||||
|
const cleanupEvents = () => {
|
||||||
|
// 移除已销毁元素的事件监听器
|
||||||
|
document.querySelectorAll('[data-cleanup]').forEach(el => {
|
||||||
|
el.removeEventListener('click', null);
|
||||||
|
el.removeEventListener('scroll', null);
|
||||||
|
el.removeEventListener('resize', null);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面卸载时清理
|
||||||
|
window.addEventListener('beforeunload', cleanupEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 网络优化
|
||||||
|
function optimizeNetwork() {
|
||||||
|
// DNS 预解析
|
||||||
|
const domains = [
|
||||||
|
'fonts.googleapis.com',
|
||||||
|
'cdnjs.cloudflare.com',
|
||||||
|
'cdn.jsdelivr.net'
|
||||||
|
];
|
||||||
|
|
||||||
|
domains.forEach(domain => {
|
||||||
|
if (!document.querySelector(`link[rel="dns-prefetch"][href*="${domain}"]`)) {
|
||||||
|
const link = document.createElement('link');
|
||||||
|
link.rel = 'dns-prefetch';
|
||||||
|
link.href = `https://${domain}`;
|
||||||
|
document.head.appendChild(link);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 预连接到关键域名
|
||||||
|
const preconnectDomains = ['fonts.gstatic.com'];
|
||||||
|
preconnectDomains.forEach(domain => {
|
||||||
|
if (!document.querySelector(`link[rel="preconnect"][href*="${domain}"]`)) {
|
||||||
|
const link = document.createElement('link');
|
||||||
|
link.rel = 'preconnect';
|
||||||
|
link.href = `https://${domain}`;
|
||||||
|
link.crossOrigin = '';
|
||||||
|
document.head.appendChild(link);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存优化
|
||||||
|
function optimizeCache() {
|
||||||
|
// Service Worker 注册(如果支持)
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
navigator.serviceWorker.register('./sw.js')
|
||||||
|
.then(registration => {
|
||||||
|
console.log('SW registered: ', registration);
|
||||||
|
})
|
||||||
|
.catch(registrationError => {
|
||||||
|
console.log('SW registration failed: ', registrationError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 本地存储优化
|
||||||
|
try {
|
||||||
|
// 缓存静态数据
|
||||||
|
if (localStorage) {
|
||||||
|
const cacheKey = 'page-cache-' + window.location.pathname;
|
||||||
|
const cacheTime = 30 * 60 * 1000; // 30分钟
|
||||||
|
|
||||||
|
const cachedData = localStorage.getItem(cacheKey);
|
||||||
|
if (cachedData) {
|
||||||
|
const { timestamp, data } = JSON.parse(cachedData);
|
||||||
|
if (Date.now() - timestamp < cacheTime) {
|
||||||
|
// 使用缓存数据
|
||||||
|
console.log('Using cached data');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('LocalStorage not available');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动画性能优化
|
||||||
|
function optimizeAnimations() {
|
||||||
|
// 检测是否需要减少动画
|
||||||
|
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||||
|
|
||||||
|
if (prefersReducedMotion) {
|
||||||
|
document.body.classList.add('reduce-motion');
|
||||||
|
|
||||||
|
// 添加减少动画的 CSS
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = `
|
||||||
|
.reduce-motion *,
|
||||||
|
.reduce-motion *::before,
|
||||||
|
.reduce-motion *::after {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 transform 和 opacity 进行动画(硬件加速)
|
||||||
|
document.querySelectorAll('[class*="animate-"]').forEach(el => {
|
||||||
|
el.style.willChange = 'transform, opacity';
|
||||||
|
|
||||||
|
// 动画完成后移除 will-change
|
||||||
|
el.addEventListener('animationend', function() {
|
||||||
|
this.style.willChange = 'auto';
|
||||||
|
}, { once: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内存优化
|
||||||
|
function optimizeMemory() {
|
||||||
|
// 定期清理无用的 DOM 元素
|
||||||
|
const cleanup = () => {
|
||||||
|
// 移除隐藏的元素(如果不再需要)
|
||||||
|
document.querySelectorAll('.hidden, [style*="display: none"]').forEach(el => {
|
||||||
|
if (el.dataset.keepHidden !== 'true') {
|
||||||
|
// 这里可以添加更智能的清理逻辑
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 每5分钟执行一次清理
|
||||||
|
setInterval(cleanup, 5 * 60 * 1000);
|
||||||
|
|
||||||
|
// 监听内存压力(如果支持)
|
||||||
|
if ('memory' in performance) {
|
||||||
|
const checkMemory = () => {
|
||||||
|
const used = performance.memory.usedJSHeapSize;
|
||||||
|
const limit = performance.memory.jsHeapSizeLimit;
|
||||||
|
const usage = used / limit;
|
||||||
|
|
||||||
|
if (usage > 0.8) {
|
||||||
|
console.warn('High memory usage detected:', Math.round(usage * 100) + '%');
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setInterval(checkMemory, 30000); // 每30秒检查一次
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 性能监控和报告
|
||||||
|
function monitorPerformance() {
|
||||||
|
// 使用 Performance Observer 监控
|
||||||
|
if ('PerformanceObserver' in window) {
|
||||||
|
// 监控 Long Tasks
|
||||||
|
const longTaskObserver = new PerformanceObserver(list => {
|
||||||
|
list.getEntries().forEach(entry => {
|
||||||
|
if (entry.duration > 50) {
|
||||||
|
console.warn('Long task detected:', entry.duration + 'ms');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
longTaskObserver.observe({ entryTypes: ['longtask'] });
|
||||||
|
} catch (e) {
|
||||||
|
// longtask 不被支持
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监控 Layout Shift
|
||||||
|
const clsObserver = new PerformanceObserver(list => {
|
||||||
|
list.getEntries().forEach(entry => {
|
||||||
|
if (entry.value > 0.1) {
|
||||||
|
console.warn('Layout shift detected:', entry.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
clsObserver.observe({ entryTypes: ['layout-shift'] });
|
||||||
|
} catch (e) {
|
||||||
|
// layout-shift 不被支持
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加性能优化样式
|
||||||
|
function addPerformanceStyles() {
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = `
|
||||||
|
/* 硬件加速 */
|
||||||
|
.gpu-accelerated {
|
||||||
|
transform: translateZ(0);
|
||||||
|
will-change: transform, opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图片加载状态 */
|
||||||
|
img.loading {
|
||||||
|
opacity: 0.3;
|
||||||
|
filter: blur(5px);
|
||||||
|
transition: opacity 0.3s, filter 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.loaded {
|
||||||
|
opacity: 1;
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.error {
|
||||||
|
opacity: 0;
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 字体加载优化 */
|
||||||
|
.font-loading {
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 减少重绘 */
|
||||||
|
.contain-layout {
|
||||||
|
contain: layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contain-paint {
|
||||||
|
contain: paint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 滚动优化 */
|
||||||
|
.scroll-smooth {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化性能优化
|
||||||
|
function init() {
|
||||||
|
if (isOptimizing) return;
|
||||||
|
isOptimizing = true;
|
||||||
|
|
||||||
|
// 立即执行的优化
|
||||||
|
optimizeResourceLoading();
|
||||||
|
optimizeCSS();
|
||||||
|
optimizeNetwork();
|
||||||
|
addPerformanceStyles();
|
||||||
|
|
||||||
|
// DOM 加载完成后执行
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
optimizeImages();
|
||||||
|
optimizeJavaScript();
|
||||||
|
optimizeAnimations();
|
||||||
|
monitorPerformance();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
optimizeImages();
|
||||||
|
optimizeJavaScript();
|
||||||
|
optimizeAnimations();
|
||||||
|
monitorPerformance();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面完全加载后执行
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
optimizeCache();
|
||||||
|
optimizeMemory();
|
||||||
|
|
||||||
|
// 测量性能
|
||||||
|
setTimeout(() => {
|
||||||
|
const perf = measurePerformance();
|
||||||
|
console.log('Performance metrics:', perf);
|
||||||
|
|
||||||
|
// 发送性能数据到分析系统(如果需要)
|
||||||
|
if (window.gtag) {
|
||||||
|
gtag('event', 'performance', {
|
||||||
|
'page_load_time': Math.round(perf.pageLoading),
|
||||||
|
'dom_load_time': Math.round(perf.domLoading)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提供外部接口
|
||||||
|
window.PerformanceOptimizer = {
|
||||||
|
measurePerformance,
|
||||||
|
optimizeImages,
|
||||||
|
getPerformanceData: () => performanceData
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
init();
|
||||||
|
})();
|
||||||
@@ -2,10 +2,22 @@
|
|||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes, viewport-fit=cover">
|
||||||
<title>预算分析 - 2024长三角国际新能源汽车展</title>
|
<title>预算分析 - 2024长三角国际新能源汽车展</title>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
|
||||||
|
<!-- 页面加载动画 -->
|
||||||
|
<!-- 错误处理(需要最早加载) -->
|
||||||
|
<script src="../js/error-handler.js"></script>
|
||||||
|
|
||||||
|
<script src="../js/page-loader.js"></script>
|
||||||
|
|
||||||
|
<!-- 移动端优化 -->
|
||||||
|
<script src="../js/mobile-optimize.js"></script>
|
||||||
|
|
||||||
|
<!-- 性能优化 -->
|
||||||
|
<script src="../js/performance-optimizer.js"></script>
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
@@ -1088,5 +1100,6 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<script src="../js/nav-component.js"></script>
|
<script src="../js/nav-component.js"></script>
|
||||||
|
<script src="../js/back-to-top.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -2,18 +2,30 @@
|
|||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes, viewport-fit=cover">
|
||||||
<title>展会介绍 - 2024长三角国际新能源汽车展</title>
|
<title>展会介绍 - 2024长三角国际新能源汽车展</title>
|
||||||
|
|
||||||
<!-- 样式表 -->
|
<!-- 样式表 -->
|
||||||
<link rel="stylesheet" href="../css/animations.css">
|
<link rel="stylesheet" href="../css/animations.css">
|
||||||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
|
||||||
<!-- 字体 -->
|
<!-- 字体 -->
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=Noto+Sans+SC:wght@300;400;500;700;900&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=Noto+Sans+SC:wght@300;400;500;700;900&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
<!-- Font Awesome 图标 -->
|
<!-- Font Awesome 图标 -->
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
|
||||||
|
<!-- 页面加载动画 -->
|
||||||
|
<!-- 错误处理(需要最早加载) -->
|
||||||
|
<script src="../js/error-handler.js"></script>
|
||||||
|
|
||||||
|
<script src="../js/page-loader.js"></script>
|
||||||
|
|
||||||
|
<!-- 移动端优化 -->
|
||||||
|
<script src="../js/mobile-optimize.js"></script>
|
||||||
|
|
||||||
|
<!-- 性能优化 -->
|
||||||
|
<script src="../js/performance-optimizer.js"></script>
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
font-family: 'Inter', 'Noto Sans SC', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
font-family: 'Inter', 'Noto Sans SC', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
@@ -794,5 +806,6 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<script src="../js/nav-component.js"></script>
|
<script src="../js/nav-component.js"></script>
|
||||||
|
<script src="../js/back-to-top.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -2,18 +2,30 @@
|
|||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes, viewport-fit=cover">
|
||||||
<title>营销方案 - 2024长三角国际新能源汽车展</title>
|
<title>营销方案 - 2024长三角国际新能源汽车展</title>
|
||||||
|
|
||||||
<!-- 样式表 -->
|
<!-- 样式表 -->
|
||||||
<link rel="stylesheet" href="../css/animations.css">
|
<link rel="stylesheet" href="../css/animations.css">
|
||||||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
|
||||||
<!-- 字体 -->
|
<!-- 字体 -->
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=Noto+Sans+SC:wght@300;400;500;700;900&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=Noto+Sans+SC:wght@300;400;500;700;900&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
<!-- Font Awesome 图标 -->
|
<!-- Font Awesome 图标 -->
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
|
||||||
|
<!-- 页面加载动画 -->
|
||||||
|
<!-- 错误处理(需要最早加载) -->
|
||||||
|
<script src="../js/error-handler.js"></script>
|
||||||
|
|
||||||
|
<script src="../js/page-loader.js"></script>
|
||||||
|
|
||||||
|
<!-- 移动端优化 -->
|
||||||
|
<script src="../js/mobile-optimize.js"></script>
|
||||||
|
|
||||||
|
<!-- 性能优化 -->
|
||||||
|
<script src="../js/performance-optimizer.js"></script>
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
font-family: 'Inter', 'Noto Sans SC', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
font-family: 'Inter', 'Noto Sans SC', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
@@ -776,5 +788,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
<script src="../js/nav-component.js"></script>
|
<script src="../js/nav-component.js"></script>
|
||||||
|
<script src="../js/back-to-top.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
File diff suppressed because it is too large
Load Diff
1405
web_frontend/web_result/pages/operation_old.html
Normal file
1405
web_frontend/web_result/pages/operation_old.html
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,13 +2,25 @@
|
|||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes, viewport-fit=cover">
|
||||||
<title>策划概述 - 2024长三角国际新能源汽车展</title>
|
<title>策划概述 - 2024长三角国际新能源汽车展</title>
|
||||||
<link rel="stylesheet" href="../css/styles.css">
|
<link rel="stylesheet" href="../css/styles.css">
|
||||||
<link rel="stylesheet" href="../css/animations.css">
|
<link rel="stylesheet" href="../css/animations.css">
|
||||||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=Noto+Sans+SC:wght@300;400;500;700;900&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=Noto+Sans+SC:wght@300;400;500;700;900&display=swap" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
|
||||||
|
<!-- 页面加载动画 -->
|
||||||
|
<!-- 错误处理(需要最早加载) -->
|
||||||
|
<script src="../js/error-handler.js"></script>
|
||||||
|
|
||||||
|
<script src="../js/page-loader.js"></script>
|
||||||
|
|
||||||
|
<!-- 移动端优化 -->
|
||||||
|
<script src="../js/mobile-optimize.js"></script>
|
||||||
|
|
||||||
|
<!-- 性能优化 -->
|
||||||
|
<script src="../js/performance-optimizer.js"></script>
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
font-family: 'Inter', 'Noto Sans SC', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
font-family: 'Inter', 'Noto Sans SC', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
@@ -482,5 +494,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
<script src="../js/nav-component.js"></script>
|
<script src="../js/nav-component.js"></script>
|
||||||
|
<script src="../js/back-to-top.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -2,10 +2,22 @@
|
|||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes, viewport-fit=cover">
|
||||||
<title>风险评估 - 2024长三角国际新能源汽车展</title>
|
<title>风险评估 - 2024长三角国际新能源汽车展</title>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
|
||||||
|
<!-- 页面加载动画 -->
|
||||||
|
<!-- 错误处理(需要最早加载) -->
|
||||||
|
<script src="../js/error-handler.js"></script>
|
||||||
|
|
||||||
|
<script src="../js/page-loader.js"></script>
|
||||||
|
|
||||||
|
<!-- 移动端优化 -->
|
||||||
|
<script src="../js/mobile-optimize.js"></script>
|
||||||
|
|
||||||
|
<!-- 性能优化 -->
|
||||||
|
<script src="../js/performance-optimizer.js"></script>
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
@@ -1041,5 +1053,6 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<script src="../js/nav-component.js"></script>
|
<script src="../js/nav-component.js"></script>
|
||||||
|
<script src="../js/back-to-top.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
283
web_frontend/web_result/sw.js
Normal file
283
web_frontend/web_result/sw.js
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
// Service Worker for performance optimization
|
||||||
|
const CACHE_NAME = 'n8n-demo-v1';
|
||||||
|
const STATIC_CACHE = 'static-v1';
|
||||||
|
const DYNAMIC_CACHE = 'dynamic-v1';
|
||||||
|
|
||||||
|
// 需要缓存的静态资源
|
||||||
|
const STATIC_FILES = [
|
||||||
|
'/',
|
||||||
|
'/index.html',
|
||||||
|
'/pages/overview.html',
|
||||||
|
'/pages/exhibition.html',
|
||||||
|
'/pages/marketing.html',
|
||||||
|
'/pages/operation.html',
|
||||||
|
'/pages/budget.html',
|
||||||
|
'/pages/risk.html',
|
||||||
|
'/js/nav-component.js',
|
||||||
|
'/js/back-to-top.js',
|
||||||
|
'/js/mobile-optimize.js',
|
||||||
|
'/js/performance-optimizer.js',
|
||||||
|
'/js/page-loader.js',
|
||||||
|
'/css/styles.css',
|
||||||
|
'/css/animations.css'
|
||||||
|
];
|
||||||
|
|
||||||
|
// 缓存策略配置
|
||||||
|
const CACHE_STRATEGIES = {
|
||||||
|
// 静态资源:缓存优先
|
||||||
|
static: {
|
||||||
|
pattern: /\.(js|css|woff|woff2|ttf|eot)$/,
|
||||||
|
strategy: 'cacheFirst',
|
||||||
|
maxAge: 30 * 24 * 60 * 60 * 1000 // 30天
|
||||||
|
},
|
||||||
|
// HTML页面:网络优先,失败时使用缓存
|
||||||
|
html: {
|
||||||
|
pattern: /\.html$/,
|
||||||
|
strategy: 'networkFirst',
|
||||||
|
maxAge: 24 * 60 * 60 * 1000 // 1天
|
||||||
|
},
|
||||||
|
// 图片:缓存优先
|
||||||
|
images: {
|
||||||
|
pattern: /\.(png|jpg|jpeg|gif|svg|webp)$/,
|
||||||
|
strategy: 'cacheFirst',
|
||||||
|
maxAge: 7 * 24 * 60 * 60 * 1000 // 7天
|
||||||
|
},
|
||||||
|
// 外部资源:网络优先
|
||||||
|
external: {
|
||||||
|
pattern: /^https:\/\/(fonts\.googleapis\.com|cdnjs\.cloudflare\.com|cdn\.jsdelivr\.net)/,
|
||||||
|
strategy: 'networkFirst',
|
||||||
|
maxAge: 7 * 24 * 60 * 60 * 1000 // 7天
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 安装事件 - 缓存静态资源
|
||||||
|
self.addEventListener('install', event => {
|
||||||
|
console.log('SW: Install event');
|
||||||
|
|
||||||
|
event.waitUntil(
|
||||||
|
caches.open(STATIC_CACHE)
|
||||||
|
.then(cache => {
|
||||||
|
console.log('SW: Caching static files');
|
||||||
|
return cache.addAll(STATIC_FILES);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
console.log('SW: Static files cached');
|
||||||
|
return self.skipWaiting();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('SW: Failed to cache static files', err);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 激活事件 - 清理旧缓存
|
||||||
|
self.addEventListener('activate', event => {
|
||||||
|
console.log('SW: Activate event');
|
||||||
|
|
||||||
|
event.waitUntil(
|
||||||
|
caches.keys()
|
||||||
|
.then(cacheNames => {
|
||||||
|
return Promise.all(
|
||||||
|
cacheNames.map(cacheName => {
|
||||||
|
// 删除旧版本的缓存
|
||||||
|
if (cacheName !== STATIC_CACHE && cacheName !== DYNAMIC_CACHE) {
|
||||||
|
console.log('SW: Deleting old cache:', cacheName);
|
||||||
|
return caches.delete(cacheName);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
console.log('SW: Old caches cleaned');
|
||||||
|
return self.clients.claim();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取事件 - 实现缓存策略
|
||||||
|
self.addEventListener('fetch', event => {
|
||||||
|
// 只处理 GET 请求
|
||||||
|
if (event.request.method !== 'GET') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(event.request.url);
|
||||||
|
|
||||||
|
// 跳过 chrome-extension 等协议
|
||||||
|
if (!url.protocol.startsWith('http')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.respondWith(handleFetch(event.request));
|
||||||
|
});
|
||||||
|
|
||||||
|
// 处理请求的核心函数
|
||||||
|
async function handleFetch(request) {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 确定缓存策略
|
||||||
|
const strategy = getCacheStrategy(request);
|
||||||
|
|
||||||
|
switch (strategy.strategy) {
|
||||||
|
case 'cacheFirst':
|
||||||
|
return await cacheFirst(request, strategy);
|
||||||
|
case 'networkFirst':
|
||||||
|
return await networkFirst(request, strategy);
|
||||||
|
case 'networkOnly':
|
||||||
|
return await fetch(request);
|
||||||
|
default:
|
||||||
|
return await networkFirst(request, strategy);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('SW: Fetch failed:', error);
|
||||||
|
|
||||||
|
// 返回离线页面或缓存的响应
|
||||||
|
return await getOfflineResponse(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取缓存策略
|
||||||
|
function getCacheStrategy(request) {
|
||||||
|
const url = request.url;
|
||||||
|
|
||||||
|
for (const [key, config] of Object.entries(CACHE_STRATEGIES)) {
|
||||||
|
if (config.pattern.test(url)) {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认策略
|
||||||
|
return { strategy: 'networkFirst', maxAge: 24 * 60 * 60 * 1000 };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存优先策略
|
||||||
|
async function cacheFirst(request, strategy) {
|
||||||
|
const cacheName = isStaticResource(request) ? STATIC_CACHE : DYNAMIC_CACHE;
|
||||||
|
const cache = await caches.open(cacheName);
|
||||||
|
const cachedResponse = await cache.match(request);
|
||||||
|
|
||||||
|
if (cachedResponse) {
|
||||||
|
// 检查缓存是否过期
|
||||||
|
const cachedDate = new Date(cachedResponse.headers.get('date') || Date.now());
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
if (now - cachedDate < strategy.maxAge) {
|
||||||
|
return cachedResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const networkResponse = await fetch(request);
|
||||||
|
|
||||||
|
if (networkResponse.ok) {
|
||||||
|
// 缓存新响应
|
||||||
|
await cache.put(request, networkResponse.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
return networkResponse;
|
||||||
|
} catch (error) {
|
||||||
|
// 网络失败,返回缓存(即使过期)
|
||||||
|
if (cachedResponse) {
|
||||||
|
return cachedResponse;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 网络优先策略
|
||||||
|
async function networkFirst(request, strategy) {
|
||||||
|
const cacheName = isStaticResource(request) ? STATIC_CACHE : DYNAMIC_CACHE;
|
||||||
|
const cache = await caches.open(cacheName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const networkResponse = await fetch(request);
|
||||||
|
|
||||||
|
if (networkResponse.ok) {
|
||||||
|
// 缓存响应
|
||||||
|
await cache.put(request, networkResponse.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
return networkResponse;
|
||||||
|
} catch (error) {
|
||||||
|
// 网络失败,尝试从缓存获取
|
||||||
|
const cachedResponse = await cache.match(request);
|
||||||
|
|
||||||
|
if (cachedResponse) {
|
||||||
|
return cachedResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否为静态资源
|
||||||
|
function isStaticResource(request) {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
return STATIC_FILES.some(file => url.pathname.endsWith(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取离线响应
|
||||||
|
async function getOfflineResponse(request) {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
|
||||||
|
// 如果是 HTML 请求,返回主页
|
||||||
|
if (request.headers.get('accept')?.includes('text/html')) {
|
||||||
|
const cache = await caches.open(STATIC_CACHE);
|
||||||
|
return await cache.match('/index.html') || new Response('Offline', { status: 503 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他资源返回 503
|
||||||
|
return new Response('Offline', { status: 503 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听消息事件
|
||||||
|
self.addEventListener('message', event => {
|
||||||
|
if (event.data && event.data.type === 'SKIP_WAITING') {
|
||||||
|
self.skipWaiting();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.data && event.data.type === 'GET_VERSION') {
|
||||||
|
event.ports[0].postMessage({ version: CACHE_NAME });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 后台同步(如果支持)
|
||||||
|
self.addEventListener('sync', event => {
|
||||||
|
if (event.tag === 'background-sync') {
|
||||||
|
event.waitUntil(doBackgroundSync());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function doBackgroundSync() {
|
||||||
|
// 这里可以执行后台同步任务
|
||||||
|
console.log('SW: Background sync');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 推送通知(如果需要)
|
||||||
|
self.addEventListener('push', event => {
|
||||||
|
if (event.data) {
|
||||||
|
const data = event.data.json();
|
||||||
|
const options = {
|
||||||
|
body: data.body,
|
||||||
|
icon: data.icon || '/favicon.ico',
|
||||||
|
badge: '/badge.png',
|
||||||
|
vibrate: [200, 100, 200],
|
||||||
|
data: data.data,
|
||||||
|
actions: data.actions
|
||||||
|
};
|
||||||
|
|
||||||
|
event.waitUntil(
|
||||||
|
self.registration.showNotification(data.title, options)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 通知点击事件
|
||||||
|
self.addEventListener('notificationclick', event => {
|
||||||
|
event.notification.close();
|
||||||
|
|
||||||
|
event.waitUntil(
|
||||||
|
clients.openWindow(event.notification.data?.url || '/')
|
||||||
|
);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user