finally finish all website check in.

This commit is contained in:
Jiao77
2025-10-01 09:51:06 +08:00
parent d40ae5a8d9
commit 96b50327f3
102 changed files with 11724 additions and 4945 deletions

View File

@@ -0,0 +1,247 @@
# 交互式EDA/CAD学术发表指南
## 📋 概述
这是一个基于《学术发表的投稿难度与周期分析》报告构建的交互式指南页面,帮助研究人员制定最佳投稿策略。
## 🎯 功能特性
### 1. 智能筛选系统
- **类型筛选**:会议 / 期刊
- **难度等级筛选**:顶级 / 高 / 应用/其他
- **评审速度筛选**:快速(<10周) / 中等(10-15周) / 慢速(>15周)
- **一键重置**:快速清除所有筛选条件
### 2. 场所卡片展示
包含7个主要发表场所
- **DAC** - Design Automation Conference (顶级会议)
- **ICCAD** - International Conference on Computer-Aided Design (顶级会议)
- **DATE** - Design, Automation and Test in Europe (高级会议)
- **ASP-DAC** - Asia and South Pacific Design Automation Conference (高级会议)
- **IEEE TCAD** - Transactions on Computer-Aided Design (顶级期刊)
- **ACM TODAES** - Transactions on Design Automation of Electronic Systems (高级期刊)
- **IEEE D&T** - Design & Test (应用期刊)
每个卡片显示:
- 🏆 难度等级
- 📊 接收率
- ⏱️ 评审速度
- ⭐ 关键指标
### 3. 交互式模态框
- **GSAP动画**:卡片到模态框的流畅展开动画
- **详细信息展示**
- 关键信息(难度、主办方、页数限制、投稿指南)
- 评审过程特点
- 可视化图表Chart.js
- 典型发表时间线
- **降级方案**GSAP未加载时使用简化模态框
### 4. 投稿策略指南
手风琴式折叠面板,包含:
- 如何理解投稿难度?
- 如何规划发表时间线?
- 如何制定投稿策略?
## 🛠️ 技术栈
### 框架与组件
- **Astro** - 主框架
- **莫兰蒂蓝色系** - 主题色 `#2c4a6b`
- **Container组件** - 玻璃态容器
- **AnimatedElement组件** - 滚动动画
### 外部依赖
- **Chart.js 4.4.1** - 数据可视化图表
- **GSAP 3.13** - 模态框动画效果
- **Tailwind CSS** - 样式框架
### 自定义脚本
`./eda-venues-interactive.ts`(本地脚本)
- TypeScript编写
- 完整类型定义
- 模块化设计
- 降级兼容处理
## 📂 文件结构
```
src/
├── pages/report/ai-eda-paper-report/
│ ├── index.astro # 主页面文件
│ ├── eda-venues-interactive.ts # 交互逻辑脚本(页面专用)
│ └── README.md # 文档说明
└── components/
├── Container.astro # 容器组件
├── AnimatedElement.astro # 动画组件
├── Header.astro # 页头组件
└── Footer.astro # 页脚组件
```
## 🎨 设计特点
### 1. 莫兰蒂蓝色系
- 主色:`#2c4a6b`
- 强调色:`#011a2d`(深蓝)
- 辅助色:`#5b778e`(浅蓝)
### 2. 玻璃态效果
```css
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
border: 1px solid rgba(44, 74, 107, 0.1);
```
### 3. 响应式布局
- 桌面3列网格
- 平板2列网格
- 移动:单列网格
### 4. 动画时序
- Hero区域200ms
- 筛选器400ms
- 场所卡片600ms
- 策略指南800ms
## 🚀 使用方式
### 访问路径
```
/report/ai-eda-paper-report
```
### 本地开发
```bash
npm run dev
# 访问 http://localhost:4321/report/ai-eda-paper-report
```
### 生产构建
```bash
npm run build
npm run preview
```
## 📊 数据结构
### VenueData接口
```typescript
interface VenueData {
id: string;
name: string;
fullName: string;
type: '会议' | '期刊';
tier: '顶级' | '高' | '应用/其他';
speed: 'fast' | 'medium' | 'slow';
speedValue: number;
acceptanceRate: string;
acceptanceValue: number | null;
keyMetric: string;
pageLimit: string;
sponsors: string;
overview: string;
reviewProcess: string[];
timeline: TimelineStage[];
guidelines: string;
}
```
### StrategyData接口
```typescript
interface StrategyData {
title: string;
content: string; // HTML内容
}
```
## 🔧 核心函数
### 主要功能函数
- `renderVenues()` - 渲染场所卡片
- `openModal(venueId, cardElement)` - 打开模态框GSAP动画
- `openSimpleModal(venueId)` - 简化模态框(降级方案)
- `closeModal()` - 关闭模态框
- `renderStrategyAccordion()` - 渲染策略手风琴
- `initEDAVenuesInteractive()` - 初始化所有交互
## ⚙️ 配置选项
### 模态框尺寸(响应式)
```typescript
// 大屏幕 (≥1024px)
targetW = '70vw';
targetH = '70vh';
// 中等屏幕
targetW = '80vw';
targetH = '75vh';
// 小屏幕 (≤640px)
targetW = '92vw';
targetH = '85vh';
```
### 动画参数
```typescript
// GSAP Timeline
duration: 0.5, // 模态框展开时长
ease: 'expo.out' // 缓动函数
```
## 🐛 错误处理
### Chart.js加载失败
```typescript
try {
if (window.Chart && venue.acceptanceValue !== null) {
// 渲染图表
}
} catch (err) {
console.warn('绘制图表时出错:', err);
}
```
### GSAP加载失败
```typescript
if (!gsap) {
console.warn('GSAP未加载使用简化模态框');
openSimpleModal(venueId);
return;
}
```
## 📝 更新日志
### 2025-10-01
- ✅ 完成页面从HTML到Astro的转换
- ✅ 创建TypeScript交互脚本
- ✅ 实现GSAP模态框动画
- ✅ 添加Chart.js图表渲染
- ✅ 实现筛选器功能
- ✅ 添加策略指南手风琴
- ✅ 实现降级兼容方案
- ✅ 应用莫兰蒂蓝色系主题
## 🎯 未来优化方向
1. **性能优化**
- 懒加载Chart.js和GSAP
- 虚拟滚动大列表
- 图片优化
2. **功能增强**
- 添加搜索功能
- 导出PDF报告
- 个性化推荐
3. **数据扩展**
- 更多会议期刊数据
- 历史录取率趋势
- 审稿人反馈统计
## 📚 相关文档
- [Astro文档](https://docs.astro.build/)
- [GSAP文档](https://greensock.com/docs/)
- [Chart.js文档](https://www.chartjs.org/docs/)
- [报告模板文档](../template/README.md)

View File

@@ -0,0 +1,739 @@
/**
* EDA/CAD学术发表指南交互式功能
* 包含场所卡片筛选、模态框动画、图表渲染等功能
*/
// 场所数据类型定义
interface TimelineStage {
stage: string;
date: string;
}
interface VenueData {
id: string;
name: string;
fullName: string;
type: '会议' | '期刊';
tier: '顶级' | '高' | '应用/其他';
speed: 'fast' | 'medium' | 'slow';
speedValue: number;
acceptanceRate: string;
acceptanceValue: number | null;
keyMetric: string;
pageLimit: string;
sponsors: string;
overview: string;
reviewProcess: string[];
timeline: TimelineStage[];
guidelines: string;
}
interface StrategyData {
title: string;
content: string;
}
// 场所数据
const venuesData: VenueData[] = [
{
id: 'dac',
name: 'DAC',
fullName: 'Design Automation Conference',
type: '会议',
tier: '顶级',
speed: 'medium',
speedValue: 14,
acceptanceRate: '20-25%',
acceptanceValue: 22.7,
keyMetric: '会议排名顶级',
pageLimit: '6页 + 1页参考文献',
sponsors: 'IEEE/ACM',
overview: 'EDA领域的顶级会议之一由IEEE和ACM共同赞助对电子行业的创新具有重要影响。',
reviewProcess: [
'研究论文采用双盲评审。',
'投稿规则非常严格,包括作者身份保密、投稿数量限制、摘要具体要求等。',
'严格的规则旨在确保评审的公正性、完整性,并管理利益冲突。'
],
timeline: [
{ stage: '建议论文开始写作', date: '每年8月' },
{ stage: '建议实验结束', date: '每年10月初' },
{ stage: '建议论文完稿', date: '每年11月初' },
{ stage: '摘要提交', date: '每年11月中旬' },
{ stage: '全文提交', date: '每年11月下旬' },
{ stage: '接收通知', date: '次年2月下旬' },
{ stage: '会议召开', date: '次年6月下旬' },
],
guidelines: '稿件长度不超过6页双栏,9或10号字体参考文献可额外占用1页需遵循IEEE模板。'
},
{
id: 'iccad',
name: 'ICCAD',
fullName: 'International Conference on Computer-Aided Design',
type: '会议',
tier: '顶级',
speed: 'fast',
speedValue: 8,
acceptanceRate: '~22%',
acceptanceValue: 22,
keyMetric: '会议排名顶级',
pageLimit: '8页 + 1页参考文献',
sponsors: 'IEEE/ACM',
overview: 'EDA研究领域的另一个顶级论坛同样由IEEE和ACM联合主办专注于集成电路和系统的CAD技术。',
reviewProcess: [
'采用双盲评审。',
'投稿分两阶段:先提交摘要,再提交全文。',
'鼓励提交包含开源软件的论文,重视可复现性和实际应用价值。'
],
timeline: [
{ stage: '建议论文开始写作', date: '每年1月' },
{ stage: '建议实验结束', date: '每年3月初' },
{ stage: '建议论文完稿', date: '每年4月初' },
{ stage: '摘要提交', date: '每年4月中旬' },
{ stage: '全文提交', date: '每年4月下旬' },
{ stage: '接收通知', date: '同年6月下旬' },
{ stage: '会议召开', date: '同年10月下旬' },
],
guidelines: '常规论文不超过8页双栏,9或10号字体参考文献可额外占用1页需遵守ACM出版政策。'
},
{
id: 'date',
name: 'DATE',
fullName: 'Design, Automation and Test in Europe',
type: '会议',
tier: '高',
speed: 'fast',
speedValue: 8,
acceptanceRate: '~36%',
acceptanceValue: 35.7,
keyMetric: 'CORE Rank B',
pageLimit: '需查阅具体征稿通知',
sponsors: 'EDA/IEEE/ACM相关',
overview: '欧洲在电子系统设计与测试领域的主要学术活动,涵盖硬件和软件,设有多个不同主题的轨道。',
reviewProcess: [
'论文经过同行评审。',
'设有多个轨道(科学论文、应用设计、最新成果等),以适应不同类型的贡献。',
'不同轨道的评审周期不同,为作者提供多样化的发表途径和速度选择。'
],
timeline: [
{ stage: '建议论文开始写作', date: '每年6月' },
{ stage: '建议实验结束', date: '每年8月初' },
{ stage: '建议论文完稿', date: '每年9月初' },
{ stage: '常规轨全文提交', date: '每年9月中旬' },
{ stage: '常规轨接收通知', date: '同年11月中旬' },
{ stage: '会议召开', date: '次年3月下旬' },
],
guidelines: '提及了音视频演示指南和海报格式要求。具体论文格式需参考当年征稿通知。'
},
{
id: 'asp-dac',
name: 'ASP-DAC',
fullName: 'Asia and South Pacific Design Automation Conference',
type: '会议',
tier: '高',
speed: 'fast',
speedValue: 8,
acceptanceRate: '~31%',
acceptanceValue: 31,
keyMetric: '区域顶级会议',
pageLimit: '6页 + 1页参考文献',
sponsors: 'IEEE/ACM相关',
overview: '亚洲及南太平洋地区关于VLSI设计自动化的年度国际会议近年来国际影响力日益提升。',
reviewProcess: [
'由技术程序委员会(TPC)进行严格、彻底的评审。',
'通常召开TPC面对面会议进行最终论文筛选注重审议和共识。',
'采用两步流程:先注册摘要,再提交完整手稿。'
],
timeline: [
{ stage: '建议论文开始写作', date: '每年4月' },
{ stage: '建议实验结束', date: '每年5月下旬' },
{ stage: '建议论文完稿', date: '每年6月下旬' },
{ stage: '摘要/全文提交', date: '每年7月上旬' },
{ stage: '接收通知', date: '同年9月上旬' },
{ stage: '会议召开', date: '次年1月下旬' },
],
guidelines: '全文双栏最多6页参考文献可额外占用1页推荐使用ACM模板。'
},
{
id: 'tcad',
name: 'IEEE TCAD',
fullName: 'Transactions on Computer-Aided Design of ICs and Systems',
type: '期刊',
tier: '顶级',
speed: 'medium',
speedValue: 10.1,
acceptanceRate: '33-34%',
acceptanceValue: 33.5,
keyMetric: 'IF: 2.9 (2022)',
pageLimit: '常规14页, 简报5页',
sponsors: 'IEEE CEDA',
overview: '月刊同行评审是集成电路与系统CAD领域的顶级期刊之一。全年滚动投稿。',
reviewProcess: [
'采用单盲评审(评审人匿名,作者身份公开)。',
'通常邀请3位评审人副编辑也可参与决策。',
'设有"desk rejection"机制,快速处理不达标或不符范围的稿件。',
'期刊正积极改善评审周期,缩短处理时间。'
],
timeline: [],
guidelines: '常规论文14页简报5页。通过Manuscript Central系统提交需遵循IEEE格式。'
},
{
id: 'todaes',
name: 'ACM TODAES',
fullName: 'Transactions on Design Automation of Electronic Systems',
type: '期刊',
tier: '高',
speed: 'slow',
speedValue: 16,
acceptanceRate: '未明确',
acceptanceValue: null,
keyMetric: 'IF: 0.852',
pageLimit: '视文章类型而定',
sponsors: 'ACM',
overview: 'ACM在电子系统设计自动化领域的旗舰期刊季刊侧重计算机科学/工程方向。全年滚动投稿。',
reviewProcess: [
'同行评审。',
'作为季刊,总发表周期可能较长,尤其在需要修改的情况下。',
'接收多种类型的稿件,包括常规研究、综述、教程等,为不同贡献提供空间。'
],
timeline: [],
guidelines: '接收常规研究论文、主旨报告、综述、教程、设计者笔记等。通过Manuscript Central系统提交遵循ACM规范。'
},
{
id: 'd&t',
name: 'IEEE D&T',
fullName: 'IEEE Design & Test',
type: '期刊',
tier: '应用/其他',
speed: 'fast',
speedValue: 6,
acceptanceRate: '未明确',
acceptanceValue: null,
keyMetric: 'IF: 1.5-1.9',
pageLimit: '特稿<5000词',
sponsors: 'IEEE',
overview: '杂志型期刊,强调实践应用、教程和案例研究,面向实践者。全年滚动投稿。',
reviewProcess: [
'同行评审,至少两位独立评审人。',
'评审侧重于论文的重要性、创新性、时下关注度、清晰度和实践相关性。',
'稿件接收可能受即将出版期次的主题契合度影响。'
],
timeline: [],
guidelines: '特稿文章不超过5000字参考文献不超过12篇。专栏文章不超过2500字。强调清晰易懂的写作风格和教程性内容。'
}
];
// 策略指南数据
const strategyData: StrategyData[] = [
{
title: '如何理解投稿难度?',
content: `
<p class="mb-2">学术出版物的"投稿难度"是一个多维度概念,不能仅看单一指标。主要考量:</p>
<ul class="list-disc list-inside space-y-2">
<li><strong>接收率 (Acceptance Rate):</strong> 接收率越低,通常意味着竞争越激烈、声望越高。但需要结合领域平均水平来看。</li>
<li><strong>影响力指标 (Impact Metrics):</strong> 如期刊影响因子(IF)、CiteScore等反映了期刊的学术影响力。Q1/Q2分区通常代表了高质量期刊。</li>
<li><strong>同行评审 (Peer Review):</strong> 评审过程的严谨性是"难度"中一个难以量化的重要部分。严格的评审流程要求稿件具有更高的质量。</li>
<li><strong>稿件质量与契合度:</strong> 稿件本身的创新性、严谨性,以及与发表场所范围的匹配度,是最根本的决定因素。</li>
</ul>
<p class="mt-4 text-sm text-slate-500"><strong>专家提示:</strong> 不要孤立地看待任何一个指标。一个接收率适中的Q1期刊其投稿难度依然很高因为它对稿件质量和潜在影响力有极高要求。</p>
`
},
{
title: '如何规划发表时间线?',
content: `
<p class="mb-2">从提交到见刊的周期受多种因素影响,关键时间节点包括:</p>
<div class="space-y-3">
<div><strong>初审 (Desk Review):</strong> 约1-2周。编辑部进行格式和范围检查部分稿件在此阶段会被直接拒稿(desk rejection)。</div>
<div><strong>同行评审 (Peer Review):</strong> 约1-3个月变动很大。这是最主要的耗时环节取决于评审人的效率。</div>
<div><strong>修改与再审 (Revision):</strong> 时间不固定。作者修改速度和编辑/评审人的二审效率都会影响总时长。</div>
<div><strong>制作与发表 (Production):</strong> 接收后约1-3个月。许多期刊提供在线预发表(Early Access),可以缩短成果传播时间。</div>
</div>
<p class="mt-4 text-sm text-slate-500"><strong>专家提示:</strong> 会议的发表周期通常比期刊更固定、更可预测。对于时间敏感的研究优先考虑会议或快速发表期刊并善用预印本服务器如arXiv。</p>
`
},
{
title: '如何制定投稿策略?',
content: `
<p class="mb-2">选择发表场所是一项战略性决策,需综合考量:</p>
<ul class="list-disc list-inside space-y-2">
<li><strong>研究性质:</strong>
<ul class="list-['-_'] list-inside ml-4">
<li><strong>开创性研究:</strong> 优先考虑顶级会议(DAC, ICCAD)或高影响力期刊(TCAD)。</li>
<li><strong>应用型/案例研究:</strong> DATE的应用轨或IEEE D&T杂志可能更合适。</li>
<li><strong>增量型研究:</strong> 可考虑区域性会议(ASP-DAC)或接收范围稍广的期刊。</li>
</ul>
</li>
<li><strong>职业目标:</strong>
<ul class="list-['-_'] list-inside ml-4">
<li><strong>博士生/早期学者:</strong> 在知名会议发表有助建立声誉和学术联系。</li>
<li><strong>职称晋升:</strong> 通常更看重高影响力(Q1/Q2)期刊论文。</li>
</ul>
</li>
<li><strong>混合策略:</strong> EDA/CAD领域的常见做法是先在顶级会议上快速发布核心创新成果之后将工作深化、扩展发表于档案型的顶级期刊。</li>
</ul>
`
}
];
// 全局变量
let activeChart: any = null;
let activeCardState: { top: number; left: number; width: number; height: number; id: string } | null = null;
let isAnimating = false;
/**
* 渲染场所卡片
*/
export function renderVenues(): void {
const venueGrid = document.getElementById('venue-grid');
const typeFilter = document.getElementById('type-filter') as HTMLSelectElement;
const tierFilter = document.getElementById('tier-filter') as HTMLSelectElement;
const speedFilter = document.getElementById('speed-filter') as HTMLSelectElement;
const noResults = document.getElementById('no-results');
if (!venueGrid || !typeFilter || !tierFilter || !speedFilter || !noResults) return;
venueGrid.innerHTML = '';
const type = typeFilter.value;
const tier = tierFilter.value;
const speed = speedFilter.value;
const filteredVenues = venuesData.filter(venue => {
const typeMatch = type === 'all' || venue.type === type;
const tierMatch = tier === 'all' || venue.tier === tier;
const speedMatch = speed === 'all' || venue.speed === speed;
return typeMatch && tierMatch && speedMatch;
});
if (filteredVenues.length === 0) {
noResults.classList.remove('hidden');
} else {
noResults.classList.add('hidden');
}
filteredVenues.forEach(venue => {
const card = document.createElement('div');
card.className = 'venue-card p-6 cursor-pointer flex flex-col';
card.dataset.id = venue.id;
card.innerHTML = `
<div class="flex-grow">
<div class="flex justify-between items-start">
<h3 class="text-2xl font-bold text-gray-800">${venue.name}</h3>
<span class="text-xs font-semibold ${venue.type === '会议' ? 'bg-sky-100 text-sky-800' : 'bg-amber-100 text-amber-800'} py-1 px-3 rounded-full">${venue.type}</span>
</div>
<p class="text-sm text-gray-500 mb-4">${venue.fullName}</p>
<div class="space-y-3 text-sm">
<div class="flex items-center">
<span class="w-6 text-center mr-2">🏆</span>
<span><strong>难度:</strong> ${venue.tier}</span>
</div>
<div class="flex items-center">
<span class="w-6 text-center mr-2">📊</span>
<span><strong>接收率:</strong> ${venue.acceptanceRate}</span>
</div>
<div class="flex items-center">
<span class="w-6 text-center mr-2">⏱️</span>
<span><strong>评审速度:</strong> ~${venue.speedValue} 周</span>
</div>
<div class="flex items-center">
<span class="w-6 text-center mr-2">⭐</span>
<span><strong>关键指标:</strong> ${venue.keyMetric}</span>
</div>
</div>
</div>
<button class="mt-6 w-full text-center bg-gray-100 text-gray-700 font-semibold py-2 rounded-lg hover:bg-gray-200 transition duration-300">查看详情</button>
`;
card.addEventListener('click', (e) => {
e.preventDefault();
openModal(venue.id, card);
});
venueGrid.appendChild(card);
});
}
/**
* 打开模态框
*/
export function openModal(venueId: string, cardElement: HTMLElement): void {
if (isAnimating) return;
// 检查是否有GSAP
const gsap = (window as any).gsap;
if (!gsap) {
console.warn('GSAP未加载使用简化模态框');
openSimpleModal(venueId);
return;
}
isAnimating = true;
const venue = venuesData.find(v => v.id === venueId);
if (!venue) {
isAnimating = false;
return;
}
const cardRect = cardElement.getBoundingClientRect();
activeCardState = {
top: cardRect.top,
left: cardRect.left,
width: cardRect.width,
height: cardRect.height,
id: venueId
};
if (activeChart) {
activeChart.destroy();
}
const modal = document.getElementById('detail-modal');
const modalOverlay = document.getElementById('modal-overlay');
const modalContent = document.getElementById('modal-content');
const modalContentWrapper = document.getElementById('modal-content-wrapper');
if (!modal || !modalOverlay || !modalContent || !modalContentWrapper) return;
// 生成时间线HTML
let timelineHtml = '';
if (venue.timeline.length > 0) {
timelineHtml = `<div class="mt-6">
<h4 class="text-lg font-semibold text-gray-800 mb-3">典型发表时间线 (${venue.type === '会议' ? '年度周期' : ''})</h4>
<ol class="relative border-l border-gray-200">`;
venue.timeline.forEach(item => {
timelineHtml += `
<li class="mb-6 ml-4">
<div class="absolute w-3 h-3 bg-gray-300 rounded-full mt-1.5 -left-1.5 border border-white"></div>
<time class="mb-1 text-sm font-normal leading-none text-gray-500">${item.date}</time>
<h3 class="text-md font-semibold text-gray-900">${item.stage}</h3>
</li>
`;
});
timelineHtml += `</ol></div>`;
}
modalContent.innerHTML = `
<div class="flex justify-between items-center mb-4">
<div>
<h2 class="text-3xl font-bold text-gray-900">${venue.name}</h2>
<p class="text-gray-600">${venue.fullName}</p>
</div>
<span class="text-sm font-semibold ${venue.type === '会议' ? 'bg-sky-100 text-sky-800' : 'bg-amber-100 text-amber-800'} py-1.5 px-4 rounded-full">${venue.type}</span>
</div>
<div class="prose max-w-none text-gray-700 mb-6">
<p>${venue.overview}</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div>
<h4 class="text-lg font-semibold text-gray-800 mb-3">关键信息</h4>
<ul class="space-y-2">
<li class="flex justify-between p-2 bg-gray-50 rounded-md"><span><strong>难度等级:</strong></span> <span class="font-semibold">${venue.tier}</span></li>
<li class="flex justify-between p-2 bg-gray-50 rounded-md"><span><strong>主办方:</strong></span> <span class="font-semibold">${venue.sponsors}</span></li>
<li class="flex justify-between p-2 bg-gray-50 rounded-md"><span><strong>页数限制:</strong></span> <span class="font-semibold">${venue.pageLimit}</span></li>
<li class="flex justify-between p-2 bg-gray-50 rounded-md"><span><strong>投稿指南:</strong></span> <span class="font-semibold">${venue.guidelines}</span></li>
</ul>
<div class="mt-6">
<h4 class="text-lg font-semibold text-gray-800 mb-3">评审过程特点</h4>
<ul class="list-disc list-inside space-y-1 text-gray-600">
${venue.reviewProcess.map(p => `<li>${p}</li>`).join('')}
</ul>
</div>
</div>
<div>
<h4 class="text-lg font-semibold text-gray-800 mb-3 text-center">指标概览</h4>
<div class="chart-container">
<canvas id="venueChart"></canvas>
</div>
${timelineHtml}
</div>
</div>
`;
// 渲染图表
try {
const Chart = (window as any).Chart;
if (Chart && venue.acceptanceValue !== null) {
const ctx = (document.getElementById('venueChart') as HTMLCanvasElement).getContext('2d');
if (ctx) {
activeChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['接收率 (%)', '评审周期 (周)'],
datasets: [{
label: venue.name,
data: [venue.acceptanceValue, venue.speedValue],
backgroundColor: ['rgba(39, 121, 127, 0.6)', 'rgba(251, 191, 36, 0.6)'],
borderColor: ['rgba(39, 121, 127, 1)', 'rgba(245, 158, 11, 1)'],
borderWidth: 1
}]
},
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } }
}
});
}
}
} catch (err) {
console.warn('绘制图表时出错:', err);
}
// GSAP动画
gsap.set(cardElement, { opacity: 0 });
modal.classList.remove('hidden');
modalOverlay.classList.remove('hidden');
const tl = gsap.timeline({
onComplete: () => { isAnimating = false; }
});
const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
let targetW = '80vw';
let targetH = '75vh';
if (vw >= 1024) {
targetW = '70vw';
targetH = '70vh';
} else if (vw <= 640) {
targetW = '92vw';
targetH = '85vh';
}
tl.set(modal, {
top: activeCardState.top,
left: activeCardState.left,
width: activeCardState.width,
height: activeCardState.height,
})
.to(modalOverlay, {
opacity: 0.6,
duration: 0.4
}, 0)
.to(modal, {
top: '50%',
left: '50%',
width: targetW,
height: targetH,
x: '-50%',
y: '-50%',
duration: 0.5,
ease: 'expo.out'
}, 0)
.to(modalContentWrapper, {
opacity: 1,
visibility: 'visible',
duration: 0.4,
}, ">-0.2");
}
/**
* 简化模态框无GSAP时使用
*/
function openSimpleModal(venueId: string): void {
const venue = venuesData.find(v => v.id === venueId);
if (!venue) return;
const modal = document.getElementById('detail-modal');
const modalOverlay = document.getElementById('modal-overlay');
const modalContent = document.getElementById('modal-content');
const modalContentWrapper = document.getElementById('modal-content-wrapper');
if (!modal || !modalOverlay || !modalContent || !modalContentWrapper) return;
// 直接显示,无动画
modal.classList.remove('hidden');
modalOverlay.classList.remove('hidden');
modalContentWrapper.style.opacity = '1';
modalContentWrapper.style.visibility = 'visible';
// 设置固定位置
modal.style.top = '50%';
modal.style.left = '50%';
modal.style.transform = 'translate(-50%, -50%)';
modal.style.width = '80vw';
modal.style.height = '75vh';
// 生成内容(同上)
let timelineHtml = '';
if (venue.timeline.length > 0) {
timelineHtml = `<div class="mt-6">
<h4 class="text-lg font-semibold text-gray-800 mb-3">典型发表时间线</h4>
<ol class="relative border-l border-gray-200">`;
venue.timeline.forEach(item => {
timelineHtml += `
<li class="mb-6 ml-4">
<div class="absolute w-3 h-3 bg-gray-300 rounded-full mt-1.5 -left-1.5 border border-white"></div>
<time class="mb-1 text-sm font-normal leading-none text-gray-500">${item.date}</time>
<h3 class="text-md font-semibold text-gray-900">${item.stage}</h3>
</li>
`;
});
timelineHtml += `</ol></div>`;
}
modalContent.innerHTML = `
<div class="flex justify-between items-center mb-4">
<div>
<h2 class="text-3xl font-bold text-gray-900">${venue.name}</h2>
<p class="text-gray-600">${venue.fullName}</p>
</div>
<span class="text-sm font-semibold ${venue.type === '会议' ? 'bg-sky-100 text-sky-800' : 'bg-amber-100 text-amber-800'} py-1.5 px-4 rounded-full">${venue.type}</span>
</div>
<div class="prose max-w-none text-gray-700 mb-6">
<p>${venue.overview}</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div>
<h4 class="text-lg font-semibold text-gray-800 mb-3">关键信息</h4>
<ul class="space-y-2">
<li class="flex justify-between p-2 bg-gray-50 rounded-md"><span><strong>难度等级:</strong></span> <span class="font-semibold">${venue.tier}</span></li>
<li class="flex justify-between p-2 bg-gray-50 rounded-md"><span><strong>主办方:</strong></span> <span class="font-semibold">${venue.sponsors}</span></li>
<li class="flex justify-between p-2 bg-gray-50 rounded-md"><span><strong>页数限制:</strong></span> <span class="font-semibold">${venue.pageLimit}</span></li>
</ul>
<div class="mt-6">
<h4 class="text-lg font-semibold text-gray-800 mb-3">评审过程特点</h4>
<ul class="list-disc list-inside space-y-1 text-gray-600">
${venue.reviewProcess.map(p => `<li>${p}</li>`).join('')}
</ul>
</div>
</div>
<div>
${timelineHtml}
</div>
</div>
`;
}
/**
* 关闭模态框
*/
export function closeModal(): void {
const gsap = (window as any).gsap;
if (!gsap || !activeCardState) {
// 简化关闭
const modal = document.getElementById('detail-modal');
const modalOverlay = document.getElementById('modal-overlay');
if (modal && modalOverlay) {
modal.classList.add('hidden');
modalOverlay.classList.add('hidden');
}
return;
}
if (isAnimating) return;
isAnimating = true;
const modal = document.getElementById('detail-modal');
const modalOverlay = document.getElementById('modal-overlay');
const modalContentWrapper = document.getElementById('modal-content-wrapper');
const originalCard = document.querySelector(`[data-id='${activeCardState.id}']`) as HTMLElement;
if (!modal || !modalOverlay || !modalContentWrapper) return;
const tl = gsap.timeline({
onComplete: () => {
modal.classList.add('hidden');
modalOverlay.classList.add('hidden');
if (originalCard) {
gsap.set(originalCard, { opacity: 1 });
}
activeCardState = null;
isAnimating = false;
}
});
tl.to(modalContentWrapper, {
opacity: 0,
duration: 0.3,
ease: 'power2.out'
})
.to(modal, {
top: activeCardState.top,
left: activeCardState.left,
width: activeCardState.width,
height: activeCardState.height,
x: '0%',
y: '0%',
duration: 0.5,
ease: 'expo.in'
}, ">-0.05")
.to(modalOverlay, {
opacity: 0,
duration: 0.4,
ease: 'power2.inOut'
}, "<")
.set(modalContentWrapper, { visibility: 'hidden' });
}
/**
* 渲染策略指南手风琴
*/
export function renderStrategyAccordion(): void {
const container = document.getElementById('accordion-container');
if (!container) return;
strategyData.forEach((item, index) => {
const div = document.createElement('div');
div.className = "bg-white rounded-xl shadow-sm overflow-hidden border-l-4 border-transparent hover:border-[#27797f] transition-all";
div.innerHTML = `
<button class="accordion-header w-full flex justify-between items-center p-5 text-left font-semibold text-gray-800 hover:bg-gray-50 transition" data-index="${index}">
<span>${item.title}</span>
<svg class="accordion-icon w-6 h-6 transform transition-transform" 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="M19 9l-7 7-7-7"></path></svg>
</button>
<div class="accordion-content hidden p-5 pt-0 prose max-w-none text-gray-600">
${item.content}
</div>
`;
container.appendChild(div);
});
document.querySelectorAll('.accordion-header').forEach(button => {
button.addEventListener('click', () => {
const content = button.nextElementSibling as HTMLElement;
const icon = button.querySelector('.accordion-icon');
content.classList.toggle('hidden');
icon?.classList.toggle('rotate-180');
});
});
}
/**
* 初始化所有交互功能
*/
export function initEDAVenuesInteractive(): void {
const typeFilter = document.getElementById('type-filter');
const tierFilter = document.getElementById('tier-filter');
const speedFilter = document.getElementById('speed-filter');
const resetButton = document.getElementById('reset-filters');
const closeModalBtn = document.getElementById('close-modal-btn');
const modalOverlay = document.getElementById('modal-overlay');
// 筛选器事件
typeFilter?.addEventListener('change', renderVenues);
tierFilter?.addEventListener('change', renderVenues);
speedFilter?.addEventListener('change', renderVenues);
resetButton?.addEventListener('click', () => {
(typeFilter as HTMLSelectElement).value = 'all';
(tierFilter as HTMLSelectElement).value = 'all';
(speedFilter as HTMLSelectElement).value = 'all';
renderVenues();
});
// 模态框关闭事件
closeModalBtn?.addEventListener('click', closeModal);
modalOverlay?.addEventListener('click', closeModal);
// 渲染初始内容
renderVenues();
renderStrategyAccordion();
console.log('🎯 EDA学术发表指南交互功能已初始化');
}

File diff suppressed because it is too large Load Diff