引言:HTML5大作业的挑战与机遇

在现代Web开发教育中,HTML5期末大作业通常要求学生综合运用所学知识,构建一个功能丰富、交互性强的单页应用(SPA)。整合多个API功能是这类作业的核心难点,也是展示技术能力的关键。本文将详细探讨如何系统性地整合四个不同类型的API,并解决开发过程中常见的技术难题。

为什么整合多个API是现代Web开发的必备技能?

整合多个API不仅能够提升应用的功能性,还能帮助学生理解:

  • 异步编程:处理多个异步请求和数据流
  • 跨域问题:理解CORS机制和解决方案
  • 数据管理:在不同数据源间进行数据融合和状态管理
  • 错误处理:构建健壮的错误处理机制
  • 性能优化:减少API调用次数,优化用户体验

一、API整合前的准备工作

1.1 理解API类型和选择策略

在开始编码前,需要明确四个API的类型。典型的组合包括:

API类型 示例 主要用途 认证方式
数据类API 天气API、新闻API 获取实时数据 API Key
地图类API 高德地图、百度地图 地理位置服务 JS API Key
媒体类API 音乐API、视频API 多媒体内容 OAuth
工具类API 翻译API、OCR API 数据处理 Token

1.2 环境准备和项目结构

# 推荐的项目结构
my-html5-project/
├── index.html          # 主页面
├── css/
│   └── style.css       # 样式文件
├── js/
│   ├── main.js         # 主逻辑
│   ├── api.js          # API封装
│   └── utils.js        # 工具函数
├── assets/             # 静态资源
└── README.md           # 项目说明

1.3 基础HTML5结构搭建

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>HTML5期末大作业 - 多API整合应用</title>
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <!-- 头部导航 -->
    <header>
        <nav>
            <ul>
                <li><a href="#weather">天气</a></li>
                <li><a href="#news">新闻</a></li>
                <li><a href="#map">地图</a></li>
                <li><a href="#translate">翻译</a></li>
            </ul>
        </nav>
    </header>

    <!-- 主内容区域 -->
    <main>
        <!-- 天气模块 -->
        <section id="weather" class="api-section">
            <h2>天气查询</h2>
            <div class="input-group">
                <input type="text" id="cityInput" placeholder="输入城市名称">
                <button onclick="getWeather()">查询</button>
            </div>
            <div id="weatherResult"></div>
        </section>

        <!-- 新闻模块 -->
        <section id="news" class="api-section">
            <h2>新闻聚合</h2>
            <button onclick="getNews()">刷新新闻</button>
            <div id="newsList"></div>
        </section>

        <!-- 地图模块 -->
        <section id="map" class="api-section">
            <h2>位置服务</h2>
            <div id="mapContainer" style="height: 400px;"></div>
            <button onclick="getCurrentLocation()">获取当前位置</button>
        </section>

        <!-- 翻译模块 -->
        <section id="translate" class="api-section">
            <h2>文本翻译</h2>
            <textarea id="sourceText" placeholder="输入要翻译的文本"></textarea>
            <button onclick="translateText()">翻译</button>
            <div id="translateResult"></div>
        </section>
    </main>

    <script src="js/utils.js"></script>
    <script src="js/api.js"></script>
    <script src="js/main.js"></script>
</body>
</html>

二、四个API功能的详细实现

2.1 API1:天气查询功能(数据类API)

2.1.1 API选择与注册

选择和风天气OpenWeatherMap作为天气数据源。以和风天气为例:

  • 注册账号获取免费API Key
  • 理解API端点:https://devapi.qweather.com/v7/weather/now

2.1.2 封装天气API调用

// js/api.js - 天气API封装
class WeatherAPI {
    constructor(apiKey) {
        this.apiKey = apiKey;
        this.baseURL = 'https://devapi.qweather.com/v7/weather';
    }

    /**
     * 获取实时天气
     * @param {string} location - 城市名称或经纬度
     * @returns {Promise} 天气数据
     */
    async getNowWeather(location) {
        try {
            const response = await fetch(
                `${this.baseURL}/now?location=${encodeURIComponent(location)}&key=${this.apiKey}`
            );
            
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            
            const data = await response.json();
            
            // 数据验证
            if (data.code !== '200') {
                throw new Error(`API错误: ${data.message}`);
            }
            
            return {
                temperature: data.now.temp,
                weather: data.now.text,
                humidity: data.now.humidity,
                wind: data.now.windDir + ' ' + data.now.windSpeed + 'km/h'
            };
            
        } catch (error) {
            console.error('天气API调用失败:', error);
            throw error;
        }
    }

    /**
     * 获取3天预报
     */
    async getForecast(location) {
        try {
            const response = await fetch(
                `${this.baseURL}/3d?location=${encodeURIComponent(location)}&key=${this.apiKey}`
            );
            const data = await response.json();
            
            if (data.code !== '200') {
                throw new Error(`API错误: ${data.message}`);
            }
            
            return data.daily.map(day => ({
                date: day.fxDate,
                maxTemp: day.tempMax,
                minTemp: day.tempMin,
                weather: day.textDay
            }));
            
        } catch (error) {
            console.error('预报API调用失败:', error);
            throw error;
        }
    }
}

2.1.3 天气UI交互逻辑

// js/main.js - 天气功能实现
async function getWeather() {
    const cityInput = document.getElementById('cityInput');
    const resultDiv = document.getElementById('weatherResult');
    const city = cityInput.value.trim();
    
    if (!city) {
        showNotification('请输入城市名称', 'error');
        return;
    }

    // 显示加载状态
    resultDiv.innerHTML = '<div class="loading">加载中...</div>';
    
    try {
        // 使用API实例
        const weatherAPI = new WeatherAPI('你的API_KEY');
        const weatherData = await weatherAPI.getNowWeather(city);
        
        // 渲染结果
        renderWeather(weatherData, resultDiv);
        
        // 缓存数据(解决重复查询问题)
        localStorage.setItem(`weather_${city}`, JSON.stringify({
            data: weatherData,
            timestamp: Date.now()
        }));
        
    } catch (error) {
        resultDiv.innerHTML = `<div class="error">查询失败: ${error.message}</div>`;
    }
}

function renderWeather(data, container) {
    container.innerHTML = `
        <div class="weather-card">
            <div class="weather-main">
                <span class="temp">${data.temperature}°C</span>
                <span class="condition">${data.weather}</span>
            </div>
            <div class="weather-details">
                <p>湿度: ${data.humidity}%</p>
                <p>风况: ${data.wind}</p>
            </div>
        </div>
    `;
}

2.2 API2:新闻聚合功能(媒体类API)

2.2.1 新闻API选择与实现

选择NewsAPIBing News Search。这里使用模拟数据展示完整流程:

// js/api.js - 新闻API封装
class NewsAPI {
    constructor(apiKey) {
        this.apiKey = apiKey;
        this.baseURL = 'https://newsapi.org/v2';
    }

    /**
     * 获取头条新闻
     * @param {string} category - 分类(business, entertainment, general, health, science, sports, technology)
     */
    async getTopHeadlines(category = 'general') {
        try {
            // 模拟API调用(实际项目中替换为真实URL)
            // const response = await fetch(
            //     `${this.baseURL}/top-headlines?country=cn&category=${category}&apiKey=${this.apiKey}`
            // );
            
            // 模拟延迟
            await new Promise(resolve => setTimeout(resolve, 800));
            
            // 模拟返回数据
            const mockData = {
                status: "ok",
                totalResults: 5,
                articles: [
                    {
                        title: "AI技术在教育领域的应用突破",
                        description: "最新研究表明,AI技术正在改变传统教学模式...",
                        url: "#",
                        urlToImage: "https://via.placeholder.com/300x200?text=AI+Education",
                        publishedAt: "2024-01-15T08:00:00Z",
                        source: { name: "Tech News" }
                    },
                    {
                        title: "新能源汽车销量创新高",
                        description: "2024年第一季度新能源汽车销量同比增长120%...",
                        url: "#",
                        urlToImage: "https://via.placeholder.com/300x200?text=EV+Sales",
                        publishedAt: "2024-01-14T12:00:00Z",
                        source: { name: "Auto Daily" }
                    }
                ]
            };
            
            if (mockData.status !== 'ok') {
                throw new Error('API返回错误状态');
            }
            
            return mockData.articles;
            
        } catch (error) {
            console.error('新闻API调用失败:', error);
            throw error;
        }
    }
}

2.2.2 新闻UI与分页处理

// js/main.js - 新闻功能实现
let currentPage = 1;
const articlesPerPage = 3;

async function getNews() {
    const newsList = document.getElementById('newsList');
    newsList.innerHTML = '<div class="loading">加载新闻中...</div>';
    
    try {
        const newsAPI = new NewsAPI('你的API_KEY');
        const articles = await newsAPI.getTopHeadlines();
        
        // 存储到全局状态
        window.newsData = articles;
        
        renderNews(articles, currentPage);
        
    } catch (error) {
        newsList.innerHTML = `<div class="error">新闻加载失败: ${error.message}</div>`;
    }
}

function renderNews(articles, page) {
    const newsList = document.getElementById('newsList');
    const start = (page - 1) * articlesPerPage;
    const end = start + articlesPerPage;
    const pageArticles = articles.slice(start, end);
    
    const newsHTML = pageArticles.map(article => `
        <article class="news-item">
            <img src="${article.urlToImage}" alt="${article.title}" onerror="this.src='https://via.placeholder.com/300x200?text=No+Image'">
            <div class="news-content">
                <h3>${article.title}</h3>
                <p>${article.description}</p>
                <div class="news-meta">
                    <span>${article.source.name}</span>
                    <span>${new Date(article.publishedAt).toLocaleDateString()}</span>
                </div>
            </div>
        </article>
    `).join('');
    
    // 添加分页控件
    const pagination = `
        <div class="pagination">
            <button onclick="changePage(${page - 1})" ${page === 1 ? 'disabled' : ''}>上一页</button>
            <span>第 ${page} / ${Math.ceil(articles.length / articlesPerPage)} 页</span>
            <button onclick="changePage(${page + 1})" ${page === Math.ceil(articles.length / articlesPerPage) ? 'disabled' : ''}>下一页</button>
        </div>
    `;
    
    newsList.innerHTML = newsHTML + pagination;
}

function changePage(newPage) {
    if (newPage < 1 || newPage > Math.ceil(window.newsData.length / articlesPerPage)) return;
    currentPage = newPage;
    renderNews(window.newsData, currentPage);
}

2.3 API3:地图功能(地理位置API)

2.3.1 地图API选择与初始化

选择高德地图JS API(国内项目推荐)或Leaflet(开源方案):

// js/api.js - 地图API封装
class MapAPI {
    constructor(apiKey) {
        this.apiKey = apiKey;
        this.map = null;
        this.geocoder = null;
    }

    /**
     * 初始化地图
     * @param {string} containerId - 容器ID
     * @param {Object} center - 中心点 {lat, lng}
     * @param {number} zoom - 缩放级别
     */
    initMap(containerId, center = { lat: 39.9042, lng: 116.4074 }, zoom = 11) {
        // 检查容器是否存在
        const container = document.getElementById(containerId);
        if (!container) {
            throw new Error(`容器 #${containerId} 不存在`);
        }

        // 模拟地图初始化(实际使用高德/百度API)
        // 示例使用Leaflet(开源)
        if (typeof L === 'undefined') {
            // 如果Leaflet未加载,使用模拟模式
            this.renderMockMap(containerId, center, zoom);
            return;
        }

        this.map = L.map(containerId).setView([center.lat, center.lng], zoom);
        
        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            attribution: '© OpenStreetMap contributors'
        }).addTo(this.map);

        return this.map;
    }

    /**
     * 模拟地图渲染(用于无API Key的情况)
     */
    renderMockMap(containerId, center, zoom) {
        const container = document.getElementById(containerId);
        container.innerHTML = `
            <div style="width:100%;height:100%;background:#e0e0e0;display:flex;align-items:center;justify-content:center;flex-direction:column;">
                <div style="font-size:48px;">🗺️</div>
                <p>地图区域(中心: ${center.lat.toFixed(2)}, ${center.lng.toFixed(2)})</p>
                <p>缩放级别: ${zoom}</p>
                <button onclick="alert('实际项目中请引入高德/百度地图API')" style="margin-top:10px;">查看API文档</button>
            </div>
        `;
    }

    /**
     * 地理编码(地址转坐标)
     */
    async geocode(address) {
        try {
            // 模拟地理编码
            await new Promise(resolve => setTimeout(resolve, 500));
            
            // 模拟返回坐标(实际应调用API)
            const mockCoordinates = {
                '北京': { lat: 39.9042, lng: 116.4074 },
                '上海': { lat: 31.2304, lng: 121.4737 },
                '广州': { lat: 23.1291, lng: 113.2644 }
            };
            
            const coord = mockCoordinates[address];
            if (!coord) {
                throw new Error('未找到该地址的坐标');
            }
            
            return coord;
            
        } catch (error) {
            console.error('地理编码失败:', error);
            throw error;
        }
    }

    /**
     * 在地图上添加标记
     */
    addMarker(lat, lng, popupText = '') {
        if (!this.map) {
            console.warn('地图未初始化');
            return;
        }

        // 模拟添加标记
        const marker = document.createElement('div');
        marker.style.cssText = `
            position: absolute;
            width: 20px;
            height: 20px;
            background: red;
            border-radius: 50%;
            transform: translate(-50%, -50%);
            cursor: pointer;
        `;
        
        // 计算像素位置(简化版)
        const container = this.map.getContainer();
        const centerX = container.offsetWidth / 2;
        const centerY = container.offsetHeight / 2;
        
        marker.style.left = centerX + 'px';
        marker.style.top = centerY + 'px';
        
        if (popupText) {
            marker.title = popupText;
            marker.onclick = () => alert(popupText);
        }
        
        container.appendChild(marker);
        
        return marker;
    }
}

2.3.2 地图功能集成

// js/main.js - 地图功能实现
let mapAPI = null;

function initMapComponent() {
    try {
        mapAPI = new MapAPI('你的地图API_KEY');
        mapAPI.initMap('mapContainer');
        showNotification('地图初始化成功', 'success');
    } catch (error) {
        console.error('地图初始化失败:', error);
        showNotification('地图初始化失败: ' + error.message, 'error');
    }
}

async function getCurrentLocation() {
    if (!navigator.geolocation) {
        showNotification('浏览器不支持地理定位', 'error');
        return;
    }

    showNotification('正在获取位置...', 'info');

    navigator.geolocation.getCurrentPosition(
        async (position) => {
            const { latitude, longitude } = position.coords;
            showNotification(`位置获取成功: ${latitude.toFixed(2)}, ${longitude.toFixed(2)}`, 'success');
            
            // 在地图上显示位置
            if (mapAPI) {
                mapAPI.addMarker(latitude, longitude, '你的当前位置');
            }
            
            // 反向地理编码(坐标转地址)
            try {
                const address = await reverseGeocode(latitude, longitude);
                showNotification(`地址: ${address}`, 'info');
            } catch (e) {
                console.warn('反向地理编码失败:', e);
            }
        },
        (error) => {
            let message = '位置获取失败';
            switch(error.code) {
                case error.PERMISSION_DENIED:
                    message = '用户拒绝了位置请求';
                    break;
                case error.POSITION_UNAVAILABLE:
                    message = '位置信息不可用';
                    break;
                case error.TIMEOUT:
                    message = '位置请求超时';
                    break;
            }
            showNotification(message, 'error');
        },
        {
            enableHighAccuracy: true,
            timeout: 10000,
            maximumAge: 60000
        }
    );
}

// 反向地理编码(坐标转地址)
async function reverseGeocode(lat, lng) {
    // 模拟反向地理编码
    await new Promise(resolve => setTimeout(resolve, 300));
    return `北纬${lat.toFixed(2)}, 东经${lng.toFixed(2)}`;
}

2.4 API4:翻译功能(工具类API)

2.4.1 翻译API实现

选择百度翻译API腾讯云翻译API

// js/api.js - 翻译API封装
class TranslateAPI {
    constructor(appId, secretKey) {
        this.appId = appId;
        this.secretKey = secretKey;
        this.baseURL = 'https://fanyi-api.baidu.com/api/trans/vip/translate';
    }

    /**
     * 文本翻译
     * @param {string} text - 要翻译的文本
     * @param {string} from - 源语言(auto表示自动检测)
     * @param {string} to - 目标语言(zh:中文, en:英文)
     */
    async translate(text, from = 'auto', to = 'zh') {
        if (!text || text.length > 2000) {
            throw new Error('文本长度必须在1-2000字符之间');
        }

        try {
            // 模拟翻译API调用(实际项目中需要签名算法)
            // 真实API需要生成签名:sign = md5(appId + text + salt + secretKey)
            await new Promise(resolve => setTimeout(resolve, 600));
            
            // 模拟翻译结果
            const mockTranslations = {
                'hello': { from: 'en', to: 'zh', trans_result: [{ src: 'hello', dst: '你好' }] },
                '你好': { from: 'zh', to: 'en', trans_result: [{ src: '你好', dst: 'hello' }] },
                'thank you': { from: 'en', to: 'zh', trans_result: [{ src: 'thank you', dst: '谢谢' }] },
                '苹果': { from: 'zh', to: 'en', trans_result: [{ src: '苹果', dst: 'apple' }] }
            };

            const lowerText = text.toLowerCase();
            const result = mockTranslations[lowerText] || mockTranslations[text];

            if (!result) {
                // 如果没有预设翻译,返回原始文本
                return {
                    from: from === 'auto' ? '未知' : from,
                    to: to,
                    text: text,
                    translation: `[模拟翻译] ${text}`
                };
            }

            return {
                from: result.from,
                to: result.to,
                text: text,
                translation: result.trans_result[0].dst
            };

        } catch (error) {
            console.error('翻译API调用失败:', error);
            throw error;
        }
    }

    /**
     * 批量翻译
     */
    async batchTranslate(texts, from = 'auto', to = 'zh') {
        const results = [];
        for (const text of texts) {
            const result = await this.translate(text, from, to);
            results.push(result);
        }
        return results;
    }
}

2.4.2 翻译UI与实时翻译

// js/main.js - 翻译功能实现
let translateAPI = null;

async function translateText() {
    const sourceText = document.getElementById('sourceText').value.trim();
    const resultDiv = document.getElementById('translateResult');
    
    if (!sourceText) {
        showNotification('请输入要翻译的文本', 'error');
        return;
    }

    // 实时翻译(输入时自动翻译)
    if (event && event.type === 'input') {
        // 防抖处理
        clearTimeout(window.translateTimer);
        window.translateTimer = setTimeout(() => {
            performTranslation(sourceText, resultDiv);
        }, 500);
    } else {
        // 按钮点击翻译
        await performTranslation(sourceText, resultDiv);
    }
}

async function performTranslation(text, container) {
    container.innerHTML = '<div class="loading">翻译中...</div>';
    
    try {
        if (!translateAPI) {
            translateAPI = new TranslateAPI('你的APP_ID', '你的密钥');
        }
        
        const result = await translateAPI.translate(text);
        
        container.innerHTML = `
            <div class="translation-result">
                <div class="source">
                    <strong>原文 (${result.from}):</strong> ${result.text}
                </div>
                <div class="target">
                    <strong>译文 (${result.to}):</strong> ${result.translation}
                </div>
            </div>
        `;
        
        // 保存历史记录
        saveTranslationHistory(text, result.translation);
        
    } catch (error) {
        container.innerHTML = `<div class="error">翻译失败: ${error.message}</div>`;
    }
}

function saveTranslationHistory(source, target) {
    const history = JSON.parse(localStorage.getItem('translationHistory') || '[]');
    history.unshift({
        source,
        target,
        timestamp: Date.now()
    });
    
    // 只保留最近10条
    if (history.length > 10) history.pop();
    
    localStorage.setItem('translationHistory', JSON.stringify(history));
}

三、整合四个API的架构设计

3.1 统一的API管理器

// js/api.js - 统一API管理器
class APIManager {
    constructor(config) {
        this.config = config;
        this.apis = {
            weather: new WeatherAPI(config.weatherKey),
            news: new NewsAPI(config.newsKey),
            map: new MapAPI(config.mapKey),
            translate: new TranslateAPI(config.appId, config.secretKey)
        };
        
        // API调用状态管理
        this.apiStatus = {
            weather: { lastCall: null, cache: null },
            news: { lastCall: null, cache: null },
            map: { lastCall: null, cache: null },
            translate: { lastCall: null, cache: null }
        };
    }

    /**
     * 带缓存的API调用
     */
    async callAPI(apiName, method, ...args) {
        const api = this.apis[apiName];
        if (!api) {
            throw new Error(`API ${apiName} 不存在`);
        }

        // 检查缓存(5分钟内有效)
        const status = this.apiStatus[apiName];
        const now = Date.now();
        const cacheKey = `${apiName}_${JSON.stringify(args)}`;
        
        if (status.cache && status.cache.key === cacheKey && 
            now - status.cache.timestamp < 300000) {
            console.log(`使用缓存的 ${apiName} 数据`);
            return status.cache.data;
        }

        try {
            const result = await api[method](...args);
            
            // 更新状态
            status.lastCall = now;
            status.cache = {
                key: cacheKey,
                data: result,
                timestamp: now
            };
            
            return result;
            
        } catch (error) {
            // 错误统计
            this.logAPIError(apiName, error);
            throw error;
        }
    }

    /**
     * 批量API调用(并行处理)
     */
    async batchCall(calls) {
        // calls: [{apiName, method, args}]
        const promises = calls.map(call => 
            this.callAPI(call.apiName, call.method, ...call.args).catch(err => ({
                error: err.message,
                api: call.apiName
            }))
        );
        
        return await Promise.all(promises);
    }

    /**
     * API错误日志
     */
    logAPIError(apiName, error) {
        const logs = JSON.parse(localStorage.getItem('apiErrors') || '[]');
        logs.push({
            api: apiName,
            error: error.message,
            timestamp: Date.now()
        });
        
        // 只保留最近20条错误日志
        if (logs.length > 20) logs.shift();
        
        localStorage.setItem('apiErrors', JSON.stringify(logs));
    }

    /**
     * 获取API使用统计
     */
    getStats() {
        const stats = {};
        for (const [apiName, status] of Object.entries(this.apiStatus)) {
            stats[apiName] = {
                lastCall: status.lastCall ? new Date(status.lastCall).toLocaleString() : '从未调用',
                hasCache: !!status.cache,
                cacheAge: status.cache ? `${((Date.now() - status.cache.timestamp) / 1000).toFixed(1)}秒` : '无'
            };
        }
        return stats;
    }
}

3.2 全局状态管理

// js/main.js - 全局状态管理
const appState = {
    apiManager: null,
    currentAPI: null,
    isLoading: false,
    errorCount: 0,
    maxRetries: 3
};

// 初始化应用
async function initApp() {
    try {
        // 从配置文件或环境变量读取API密钥
        const config = {
            weatherKey: '你的和风天气Key',
            newsKey: '你的NewsAPI Key',
            mapKey: '你的地图Key',
            appId: '你的翻译AppID',
            secretKey: '你的翻译密钥'
        };

        // 验证配置
        if (!config.weatherKey || config.weatherKey === '你的和风天气Key') {
            showNotification('请配置API密钥', 'warning');
        }

        appState.apiManager = new APIManager(config);
        
        // 初始化地图组件
        initMapComponent();
        
        // 绑定事件
        bindEvents();
        
        showNotification('应用初始化完成', 'success');
        
    } catch (error) {
        console.error('应用初始化失败:', error);
        showNotification('初始化失败: ' + error.message, 'error');
    }
}

function bindEvents() {
    // 翻译实时输入
    const sourceText = document.getElementById('sourceText');
    if (sourceText) {
        sourceText.addEventListener('input', () => {
            clearTimeout(window.debounceTimer);
            window.debounceTimer = setTimeout(() => {
                if (sourceText.value.trim()) {
                    translateText();
                }
            }, 500);
        });
    }

    // 天气查询回车键支持
    const cityInput = document.getElementById('cityInput');
    if (cityInput) {
        cityInput.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') getWeather();
        });
    }

    // 页面可见性变化时的处理
    document.addEventListener('visibilitychange', () => {
        if (document.hidden) {
            console.log('页面隐藏,暂停API调用');
        } else {
            console.log('页面显示,恢复服务');
        }
    });
}

3.3 错误处理与重试机制

// js/utils.js - 错误处理与重试
class APIError extends Error {
    constructor(message, apiName, originalError) {
        super(message);
        this.apiName = apiName;
        this.originalError = originalError;
        this.isRetryable = this.checkRetryable(originalError);
    }

    checkRetryable(error) {
        // 判断是否可重试
        const retryableStatus = [408, 429, 500, 502, 503, 504];
        const retryableMessages = ['timeout', 'network', 'rate limit'];
        
        if (error.status && retryableStatus.includes(error.status)) {
            return true;
        }
        
        if (error.message) {
            return retryableMessages.some(msg => 
                error.message.toLowerCase().includes(msg)
            );
        }
        
        return false;
    }
}

/**
 * 带重试的API调用
 */
async function callAPIWithRetry(apiCall, maxRetries = 3, delay = 1000) {
    let lastError;
    
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            return await apiCall();
        } catch (error) {
            lastError = error;
            
            // 包装为APIError
            const apiError = new APIError(
                `API调用失败 (尝试 ${attempt}/${maxRetries})`,
                'unknown',
                error
            );
            
            if (!apiError.isRetryable || attempt === maxRetries) {
                throw apiError;
            }
            
            // 指数退避延迟
            const waitTime = delay * Math.pow(2, attempt - 1);
            console.log(`等待 ${waitTime}ms 后重试...`);
            await new Promise(resolve => setTimeout(resolve, waitTime));
        }
    }
    
    throw lastError;
}

/**
 * 全局错误处理器
 */
function handleAPIError(error, context) {
    console.error(`[${context}] API错误:`, error);
    
    // 更新错误计数
    appState.errorCount++;
    
    // 显示用户友好的错误信息
    let userMessage = '操作失败,请稍后重试';
    
    if (error instanceof APIError) {
        if (error.isRetryable) {
            userMessage = '网络连接问题,正在自动重试...';
        } else if (error.message.includes('API密钥')) {
            userMessage = 'API密钥无效,请检查配置';
        } else if (error.message.includes('限流')) {
            userMessage = '请求过于频繁,请稍后再试';
        }
    }
    
    showNotification(userMessage, 'error');
    
    // 错误日志
    if (appState.errorCount > 5) {
        showNotification('错误次数过多,请检查网络连接', 'warning');
    }
}

四、常见开发难题与解决方案

4.1 跨域问题(CORS)

问题描述:浏览器安全策略阻止不同源的API请求。

解决方案

// 方案1:使用代理服务器(推荐)
// 在开发环境中,可以配置webpack/vite代理
// vite.config.js
export default {
    server: {
        proxy: {
            '/api/weather': {
                target: 'https://devapi.qweather.com',
                changeOrigin: true,
                rewrite: (path) => path.replace(/^\/api\/weather/, '')
            }
        }
    }
}

// 然后在代码中:
fetch('/api/weather/now?location=beijing&key=xxx')

// 方案2:使用CORS代理服务
const CORS_PROXY = 'https://cors-anywhere.herokuapp.com/';
fetch(CORS_PROXY + 'https://api.example.com/data')

// 方案3:JSONP(仅适用于支持JSONP的API)
function jsonp(url, callback) {
    const callbackName = 'jsonp_' + Date.now();
    const script = document.createElement('script');
    
    window[callbackName] = function(data) {
        delete window[callbackName];
        document.body.removeChild(script);
        callback(data);
    };
    
    script.src = url + (url.includes('?') ? '&' : '?') + 'callback=' + callbackName;
    document.body.appendChild(script);
}

// 使用示例
jsonp('https://api.example.com/data?param=value', (data) => {
    console.log('收到数据:', data);
});

4.2 异步编程与Promise处理

问题:多个API调用时的异步控制流复杂。

解决方案

// 方案1:Promise.all - 并行调用
async function fetchAllData() {
    try {
        const [weather, news, translation] = await Promise.all([
            appState.apiManager.callAPI('weather', 'getNowWeather', '北京'),
            appState.apiManager.callAPI('news', 'getTopHeadlines', 'general'),
            appState.apiManager.callAPI('translate', 'translate', 'Hello World')
        ]);
        
        console.log('所有数据加载完成');
        return { weather, news, translation };
        
    } catch (error) {
        console.error('并行调用失败:', error);
        // 注意:Promise.all只要有一个失败就会全部失败
    }
}

// 方案2:Promise.allSettled - 容错并行调用
async function fetchAllDataSafe() {
    const results = await Promise.allSettled([
        appState.apiManager.callAPI('weather', 'getNowWeather', '北京'),
        appState.apiManager.callAPI('news', 'getTopHeadlines', 'general'),
        appState.apiManager.callAPI('translate', 'translate', 'Hello World')
    ]);
    
    const data = {};
    results.forEach((result, index) => {
        if (result.status === 'fulfilled') {
            const apiName = ['weather', 'news', 'translate'][index];
            data[apiName] = result.value;
        } else {
            console.error(`API ${index} 失败:`, result.reason);
        }
    });
    
    return data;
}

// 方案3:串行调用(有依赖关系时)
async function fetchSequentially() {
    // 先翻译,再用翻译结果查询新闻
    const translation = await appState.apiManager.callAPI('translate', 'translate', 'technology');
    const news = await appState.apiManager.callAPI('news', 'getTopHeadlines', 'technology');
    
    return { translation, news };
}

// 方案4:带并发控制的批量调用
async function fetchWithConcurrency(apiCalls, maxConcurrent = 2) {
    const results = [];
    const executing = [];
    
    for (let i = 0; i < apiCalls.length; i++) {
        const promise = appState.apiManager.callAPI(
            apiCalls[i].apiName,
            apiCalls[i].method,
            ...apiCalls[i].args
        ).then(result => {
            results[i] = result;
            executing.splice(executing.indexOf(promise), 1);
        });
        
        executing.push(promise);
        
        if (executing.length >= maxConcurrent) {
            await Promise.race(executing);
        }
    }
    
    await Promise.all(executing);
    return results;
}

4.3 数据缓存与性能优化

问题:频繁调用API导致性能问题和配额浪费。

解决方案

// js/utils.js - 缓存管理器
class CacheManager {
    constructor(defaultTTL = 300000) { // 5分钟默认TTL
        this.defaultTTL = defaultTTTL;
    }

    /**
     * 生成缓存键
     */
    generateKey(prefix, params) {
        const paramStr = JSON.stringify(params || {});
        return `${prefix}:${paramStr}`;
    }

    /**
     * 设置缓存
     */
    set(key, data, ttl = null) {
        const expireTime = Date.now() + (ttl || this.defaultTTL);
        const cacheData = {
            data: data,
            expire: expireTime
        };
        
        try {
            localStorage.setItem(key, JSON.stringify(cacheData));
            return true;
        } catch (e) {
            // localStorage已满,清理旧数据
            this.cleanup();
            return false;
        }
    }

    /**
     * 获取缓存
     */
    get(key) {
        try {
            const item = localStorage.getItem(key);
            if (!item) return null;
            
            const cacheData = JSON.parse(item);
            
            // 检查是否过期
            if (Date.now() > cacheData.expire) {
                localStorage.removeItem(key);
                return null;
            }
            
            return cacheData.data;
            
        } catch (e) {
            return null;
        }
    }

    /**
     * 清理过期缓存
     */
    cleanup() {
        const keys = Object.keys(localStorage);
        const now = Date.now();
        let cleaned = 0;
        
        keys.forEach(key => {
            if (key.startsWith('cache:')) {
                try {
                    const item = localStorage.getItem(key);
                    const data = JSON.parse(item);
                    if (now > data.expire) {
                        localStorage.removeItem(key);
                        cleaned++;
                    }
                } catch (e) {
                    localStorage.removeItem(key);
                }
            }
        });
        
        console.log(`清理了 ${cleaned} 个过期缓存`);
    }

    /**
     * 清空所有缓存
     */
    clear() {
        const keys = Object.keys(localStorage);
        keys.forEach(key => {
            if (key.startsWith('cache:')) {
                localStorage.removeItem(key);
            }
        });
    }
}

// 使用缓存的API调用
async function cachedAPICall(apiName, method, ...args) {
    const cacheManager = new CacheManager();
    const cacheKey = cacheManager.generateKey(`api:${apiName}:${method}`, args);
    
    // 尝试从缓存读取
    const cached = cacheManager.get(cacheKey);
    if (cached !== null) {
        console.log(`从缓存返回 ${apiName}.${method}`);
        return cached;
    }
    
    // 调用API
    const result = await appState.apiManager.callAPI(apiName, method, ...args);
    
    // 存入缓存
    cacheManager.set(cacheKey, result);
    
    return result;
}

4.4 用户体验优化(加载状态与错误处理)

// js/utils.js - UI状态管理
class UIStateManager {
    constructor() {
        this.loadingStack = [];
    }

    /**
     * 显示加载状态
     */
    showLoading(message = '加载中...', targetId = null) {
        const loadingId = 'loading_' + Date.now() + '_' + Math.random();
        
        if (targetId) {
            const target = document.getElementById(targetId);
            if (target) {
                target.innerHTML = `<div class="loading">${message}</div>`;
                target.dataset.loadingId = loadingId;
            }
        } else {
            // 全局加载指示器
            this.showGlobalLoading(message, loadingId);
        }
        
        this.loadingStack.push(loadingId);
        return loadingId;
    }

    /**
     * 隐藏加载状态
     */
    hideLoading(loadingId) {
        const index = this.loadingStack.indexOf(loadingId);
        if (index > -1) {
            this.loadingStack.splice(index, 1);
        }
        
        // 移除全局加载指示器
        const globalLoading = document.getElementById('globalLoading');
        if (globalLoading && this.loadingStack.length === 0) {
            globalLoading.style.display = 'none';
        }
        
        // 移除目标元素的加载状态
        const target = document.querySelector(`[data-loading-id="${loadingId}"]`);
        if (target) {
            target.innerHTML = '';
            delete target.dataset.loadingId;
        }
    }

    /**
     * 显示通知
     */
    showNotification(message, type = 'info', duration = 3000) {
        const notification = document.createElement('div');
        notification.className = `notification notification-${type}`;
        notification.textContent = message;
        
        // 样式
        notification.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            padding: 12px 20px;
            background: ${this.getNotificationColor(type)};
            color: white;
            border-radius: 4px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.2);
            z-index: 10000;
            animation: slideIn 0.3s ease-out;
        `;
        
        document.body.appendChild(notification);
        
        setTimeout(() => {
            notification.style.animation = 'fadeOut 0.3s ease-in';
            setTimeout(() => notification.remove(), 300);
        }, duration);
    }

    getNotificationColor(type) {
        const colors = {
            info: '#2196F3',
            success: '#4CAF50',
            warning: '#FF9800',
            error: '#F44336'
        };
        return colors[type] || colors.info;
    }

    showGlobalLoading(message, loadingId) {
        let loading = document.getElementById('globalLoading');
        if (!loading) {
            loading = document.createElement('div');
            loading.id = 'globalLoading';
            loading.style.cssText = `
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background: rgba(0,0,0,0.5);
                display: flex;
                align-items: center;
                justify-content: center;
                z-index: 9999;
                color: white;
                font-size: 18px;
            `;
            document.body.appendChild(loading);
        }
        
        loading.innerHTML = `
            <div style="text-align:center">
                <div class="spinner" style="width:40px;height:40px;border:4px solid #fff;border-top-color:transparent;border-radius:50%;animation:spin 1s linear infinite;margin:0 auto 10px;"></div>
                ${message}
            </div>
        `;
        loading.style.display = 'flex';
    }
}

// 全局通知函数
function showNotification(message, type = 'info', duration = 3000) {
    if (!window.uiStateManager) {
        window.uiStateManager = new UIStateManager();
    }
    window.uiStateManager.showNotification(message, type, duration);
}

4.5 移动端适配与触摸事件

// js/utils.js - 移动端优化
function setupMobileOptimization() {
    // 禁用双击缩放
    let lastTouchEnd = 0;
    document.addEventListener('touchend', (event) => {
        const now = Date.now();
        if (now - lastTouchEnd <= 300) {
            event.preventDefault();
        }
        lastTouchEnd = now;
    }, false);

    // 触摸反馈
    document.querySelectorAll('button, .touchable').forEach(el => {
        el.addEventListener('touchstart', () => {
            el.style.opacity = '0.7';
        });
        el.addEventListener('touchend', () => {
            setTimeout(() => el.style.opacity = '1', 100);
        });
    });

    // 防止页面滚动穿透
    document.addEventListener('touchmove', (e) => {
        if (e.target.closest('.modal')) {
            e.preventDefault();
        }
    }, { passive: false });
}

// 触摸滑动切换API模块
function setupSwipeNavigation() {
    let startX = 0;
    let startY = 0;
    const sections = document.querySelectorAll('.api-section');
    let currentIndex = 0;

    document.addEventListener('touchstart', (e) => {
        startX = e.touches[0].clientX;
        startY = e.touches[0].clientY;
    });

    document.addEventListener('touchend', (e) => {
        const endX = e.changedTouches[0].clientX;
        const endY = e.changedTouches[0].clientY;
        const diffX = startX - endX;
        const diffY = startY - endY;

        // 水平滑动超过50px且垂直滑动较小
        if (Math.abs(diffX) > 50 && Math.abs(diffY) < 50) {
            if (diffX > 0 && currentIndex < sections.length - 1) {
                // 向左滑动,下一个
                currentIndex++;
                sections[currentIndex].scrollIntoView({ behavior: 'smooth' });
            } else if (diffX < 0 && currentIndex > 0) {
                // 向右滑动,上一个
                currentIndex--;
                sections[currentIndex].scrollIntoView({ behavior: 'smooth' });
            }
        }
    });
}

五、完整项目整合与部署

5.1 主入口文件整合

// js/main.js - 完整整合
document.addEventListener('DOMContentLoaded', async () => {
    try {
        // 1. 初始化UI状态管理
        window.uiStateManager = new UIStateManager();
        
        // 2. 初始化API管理器
        await initApp();
        
        // 3. 移动端优化
        setupMobileOptimization();
        setupSwipeNavigation();
        
        // 4. 绑定所有事件
        bindAllEvents();
        
        // 5. 预加载数据(可选)
        preloadData();
        
        showNotification('应用加载完成', 'success');
        
    } catch (error) {
        console.error('应用启动失败:', error);
        showNotification('应用启动失败: ' + error.message, 'error');
    }
});

function bindAllEvents() {
    // 天气查询
    const weatherBtn = document.querySelector('#weather button');
    if (weatherBtn) {
        weatherBtn.addEventListener('click', getWeather);
    }

    // 新闻刷新
    const newsBtn = document.querySelector('#news button');
    if (newsBtn) {
        newsBtn.addEventListener('click', getNews);
    }

    // 地图定位
    const mapBtn = document.querySelector('#map button');
    if (mapBtn) {
        mapBtn.addEventListener('click', getCurrentLocation);
    }

    // 翻译
    const translateBtn = document.querySelector('#translate button');
    if (translateBtn) {
        translateBtn.addEventListener('click', translateText);
    }

    // 键盘快捷键
    document.addEventListener('keydown', (e) => {
        // Ctrl+Shift+W: 快速天气查询
        if (e.ctrlKey && e.shiftKey && e.key === 'W') {
            e.preventDefault();
            document.getElementById('cityInput').focus();
        }
        // Ctrl+Shift+N: 刷新新闻
        if (e.ctrlKey && e.shiftKey && e.key === 'N') {
            e.preventDefault();
            getNews();
        }
    });
}

function preloadData() {
    // 预加载缓存数据
    const cacheManager = new CacheManager();
    
    // 检查是否有上次的城市天气缓存
    const lastCity = localStorage.getItem('lastCity');
    if (lastCity) {
        document.getElementById('cityInput').value = lastCity;
        // 延迟加载,避免阻塞初始渲染
        setTimeout(() => getWeather(), 1000);
    }
}

5.2 CSS样式(增强用户体验)

/* css/style.css */
:root {
    --primary-color: #4285f4;
    --success-color: #34a853;
    --warning-color: #fbbc05;
    --error-color: #ea4335;
    --bg-light: #f8f9fa;
    --text-dark: #202124;
    --border-radius: 8px;
    --shadow: 0 2px 6px rgba(0,0,0,0.1);
}

* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    line-height: 1.6;
    color: var(--text-dark);
    background: var(--bg-light);
}

header {
    background: white;
    box-shadow: var(--shadow);
    position: sticky;
    top: 0;
    z-index: 100;
}

nav ul {
    display: flex;
    list-style: none;
    padding: 0 20px;
}

nav li {
    margin-right: 20px;
}

nav a {
    display: block;
    padding: 15px 0;
    text-decoration: none;
    color: var(--text-dark);
    font-weight: 500;
    transition: color 0.3s;
}

nav a:hover {
    color: var(--primary-color);
}

main {
    max-width: 1200px;
    margin: 0 auto;
    padding: 20px;
}

.api-section {
    background: white;
    padding: 25px;
    margin-bottom: 20px;
    border-radius: var(--border-radius);
    box-shadow: var(--shadow);
    scroll-margin-top: 80px;
}

.api-section h2 {
    margin-bottom: 20px;
    color: var(--primary-color);
    border-bottom: 2px solid var(--bg-light);
    padding-bottom: 10px;
}

.input-group {
    display: flex;
    gap: 10px;
    margin-bottom: 15px;
}

input[type="text"], textarea {
    flex: 1;
    padding: 10px;
    border: 2px solid #ddd;
    border-radius: var(--border-radius);
    font-size: 16px;
    transition: border-color 0.3s;
}

input[type="text"]:focus, textarea:focus {
    outline: none;
    border-color: var(--primary-color);
}

textarea {
    width: 100%;
    min-height: 100px;
    resize: vertical;
}

button {
    padding: 10px 20px;
    background: var(--primary-color);
    color: white;
    border: none;
    border-radius: var(--border-radius);
    cursor: pointer;
    font-size: 16px;
    font-weight: 500;
    transition: all 0.3s;
}

button:hover:not(:disabled) {
    background: #3367d6;
    transform: translateY(-1px);
    box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}

button:disabled {
    background: #ccc;
    cursor: not-allowed;
    transform: none;
}

button:active:not(:disabled) {
    transform: translateY(0);
}

/* 天气卡片 */
.weather-card {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    padding: 20px;
    border-radius: var(--border-radius);
    margin-top: 15px;
}

.weather-main {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 15px;
}

.temp {
    font-size: 48px;
    font-weight: bold;
}

.condition {
    font-size: 24px;
}

.weather-details p {
    margin: 5px 0;
    opacity: 0.9;
}

/* 新闻列表 */
.news-item {
    display: flex;
    gap: 15px;
    padding: 15px;
    border: 1px solid #eee;
    border-radius: var(--border-radius);
    margin-bottom: 10px;
    transition: all 0.3s;
}

.news-item:hover {
    box-shadow: var(--shadow);
    transform: translateX(5px);
}

.news-item img {
    width: 120px;
    height: 80px;
    object-fit: cover;
    border-radius: 4px;
    flex-shrink: 0;
}

.news-content {
    flex: 1;
}

.news-content h3 {
    margin-bottom: 8px;
    font-size: 18px;
}

.news-content p {
    font-size: 14px;
    color: #666;
    margin-bottom: 8px;
}

.news-meta {
    display: flex;
    gap: 15px;
    font-size: 12px;
    color: #999;
}

/* 分页 */
.pagination {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 15px;
    margin-top: 20px;
}

.pagination button {
    padding: 8px 16px;
    background: white;
    color: var(--primary-color);
    border: 2px solid var(--primary-color);
}

.pagination button:hover:not(:disabled) {
    background: var(--primary-color);
    color: white;
}

.pagination span {
    font-weight: 500;
}

/* 地图容器 */
#mapContainer {
    background: #e8eaed;
    border-radius: var(--border-radius);
    margin-top: 15px;
    position: relative;
    overflow: hidden;
}

/* 翻译结果 */
.translation-result {
    background: #f8f9fa;
    padding: 15px;
    border-radius: var(--border-radius);
    margin-top: 15px;
    border-left: 4px solid var(--success-color);
}

.translation-result .source {
    margin-bottom: 10px;
    color: #666;
}

.translation-result .target {
    font-size: 18px;
    color: var(--text-dark);
    font-weight: 500;
}

/* 加载和错误状态 */
.loading {
    text-align: center;
    padding: 20px;
    color: #666;
    font-style: italic;
}

.error {
    background: #fee;
    color: var(--error-color);
    padding: 15px;
    border-radius: var(--border-radius);
    border-left: 4px solid var(--error-color);
    margin-top: 15px;
}

/* 动画 */
@keyframes slideIn {
    from {
        transform: translateX(100%);
        opacity: 0;
    }
    to {
        transform: translateX(0);
        opacity: 1;
    }
}

@keyframes fadeOut {
    from {
        opacity: 1;
    }
    to {
        opacity: 0;
    }
}

@keyframes spin {
    to { transform: rotate(360deg); }
}

/* 响应式设计 */
@media (max-width: 768px) {
    nav ul {
        flex-wrap: wrap;
        padding: 0 10px;
    }
    
    nav li {
        margin-right: 10px;
    }
    
    nav a {
        padding: 10px 5px;
        font-size: 14px;
    }
    
    main {
        padding: 10px;
    }
    
    .api-section {
        padding: 15px;
    }
    
    .input-group {
        flex-direction: column;
    }
    
    .news-item {
        flex-direction: column;
    }
    
    .news-item img {
        width: 100%;
        height: 150px;
    }
    
    .weather-main {
        flex-direction: column;
        align-items: flex-start;
        gap: 10px;
    }
    
    .temp {
        font-size: 36px;
    }
    
    .condition {
        font-size: 18px;
    }
}

/* 暗色模式支持 */
@media (prefers-color-scheme: dark) {
    :root {
        --bg-light: #202124;
        --text-dark: #e8eaed;
    }
    
    body {
        background: #202124;
        color: #e8eaed;
    }
    
    header, .api-section, .news-item, button {
        background: #292a2d;
        color: #e8eaed;
        border-color: #3c4043;
    }
    
    input[type="text"], textarea {
        background: #3c4043;
        color: #e8eaed;
        border-color: #5f6368;
    }
    
    .news-content p, .news-meta {
        color: #9aa0a6;
    }
}

5.3 项目配置与API密钥管理

// config.js - 配置管理(不要提交到版本控制)
const API_CONFIG = {
    // 和风天气API
    WEATHER_API_KEY: 'your_qweather_key_here',
    
    // NewsAPI
    NEWS_API_KEY: 'your_newsapi_key_here',
    
    // 高德地图API(JS API)
    MAP_API_KEY: 'your_amap_key_here',
    
    // 百度翻译API
    BAIDU_APP_ID: 'your_baidu_app_id',
    BAIDU_SECRET_KEY: 'your_baidu_secret_key',
    
    // 其他配置
    CACHE_TTL: 300000, // 5分钟
    MAX_RETRIES: 3,
    API_TIMEOUT: 10000
};

// 环境变量检测
function validateConfig() {
    const missing = [];
    
    if (!API_CONFIG.WEATHER_API_KEY || API_CONFIG.WEATHER_API_KEY.includes('your_')) {
        missing.push('WEATHER_API_KEY');
    }
    
    if (!API_CONFIG.NEWS_API_KEY || API_CONFIG.NEWS_API_KEY.includes('your_')) {
        missing.push('NEWS_API_KEY');
    }
    
    if (missing.length > 0) {
        console.warn('缺少API配置:', missing.join(', '));
        return false;
    }
    
    return true;
}

// 导出配置
if (typeof module !== 'undefined' && module.exports) {
    module.exports = { API_CONFIG, validateConfig };
}

5.4 部署与测试建议

// test.js - 简单的测试脚本
async function runTests() {
    console.log('开始API整合测试...\n');
    
    const testCases = [
        {
            name: '天气API测试',
            test: async () => {
                const weather = new WeatherAPI(API_CONFIG.WEATHER_API_KEY);
                const data = await weather.getNowWeather('北京');
                return data.temperature !== undefined;
            }
        },
        {
            name: '新闻API测试',
            test: async () => {
                const news = new NewsAPI(API_CONFIG.NEWS_API_KEY);
                const data = await news.getTopHeadlines();
                return Array.isArray(data) && data.length > 0;
            }
        },
        {
            name: '翻译API测试',
            test: async () => {
                const translate = new TranslateAPI(API_CONFIG.BAIDU_APP_ID, API_CONFIG.BAIDU_SECRET_KEY);
                const result = await translate.translate('hello');
                return result.translation === '你好';
            }
        },
        {
            name: '缓存测试',
            test: async () => {
                const cache = new CacheManager();
                cache.set('test', { value: 123 }, 60000);
                const data = cache.get('test');
                return data && data.value === 123;
            }
        }
    ];

    let passed = 0;
    let failed = 0;

    for (const testCase of testCases) {
        try {
            const result = await testCase.test();
            if (result) {
                console.log(`✅ ${testCase.name}: 通过`);
                passed++;
            } else {
                console.log(`❌ ${testCase.name}: 失败`);
                failed++;
            }
        } catch (error) {
            console.log(`❌ ${testCase.name}: 错误 - ${error.message}`);
            failed++;
        }
    }

    console.log(`\n测试结果: ${passed}/${testCases.length} 通过`);
    
    if (failed > 0) {
        console.log('\n⚠️  部分测试失败,请检查API配置和网络连接');
    }
}

// 如果是Node.js环境,运行测试
if (typeof module !== 'undefined' && module.exports) {
    // 导出测试函数
    module.exports = { runTests };
}

六、总结与最佳实践

6.1 关键要点回顾

  1. API封装:每个API独立封装,提供清晰的接口
  2. 错误处理:统一的错误处理机制,包含重试逻辑
  3. 缓存策略:减少API调用,提升性能
  4. 状态管理:全局状态管理,避免回调地狱
  5. 用户体验:加载状态、错误提示、移动端适配

6.2 性能优化清单

  • [ ] 使用缓存减少重复API调用
  • [ ] 并行调用独立API(Promise.all)
  • [ ] 图片懒加载和错误处理
  • [ ] 代码分割和按需加载
  • [ ] 使用Web Workers处理复杂计算(如果需要)
  • [ ] 启用Gzip压缩(服务器配置)

6.3 安全注意事项

  • API密钥保护:不要硬编码在前端代码中,使用后端代理
  • 输入验证:对所有用户输入进行验证和清理
  • HTTPS:确保所有API调用使用HTTPS
  • CORS策略:正确配置CORS,避免跨域风险
  • 错误信息:不要向用户暴露敏感错误信息

6.4 扩展建议

  1. 添加更多API:如语音识别、图像识别、OCR等
  2. 实现离线功能:使用Service Worker和IndexedDB
  3. 添加PWA支持:使应用可安装到桌面
  4. 实现数据可视化:使用Chart.js展示API数据
  5. 添加用户系统:保存用户偏好和历史记录

通过以上详细的实现方案,你可以构建一个功能完整、架构清晰、用户体验优秀的HTML5期末大作业。记住,关键在于模块化设计错误处理性能优化。祝你项目成功!