开发者工具扩展:DevTools Panel开发指南

开发者工具扩展:DevTools Panel开发指南

【免费下载链接】chrome-extension-boilerplate-react-vite Chrome Extension Boilerplate with React + Vite + Typescript 【免费下载链接】chrome-extension-boilerplate-react-vite 项目地址: https://gitcode.com/GitHub_Trending/ch/chrome-extension-boilerplate-react-vite

本文深入解析了Chrome扩展DevTools Panel的开发架构、实现技术和最佳实践。内容涵盖DevTools扩展的核心架构解析、自定义面板开发技术、与页面元素的交互通信机制,以及性能监控与调试技巧。文章详细介绍了使用React + Vite + TypeScript技术栈开发自定义DevTools面板的方法,包括模块化设计、依赖管理、消息传递机制和性能优化策略,为开发者提供了完整的开发指南和实用解决方案。

DevTools扩展架构解析

Chrome扩展的DevTools Panel开发架构是一个精心设计的系统,它通过多个组件协同工作来实现开发者工具的扩展功能。让我们深入分析这个架构的核心组成部分和工作原理。

架构概览

DevTools扩展架构主要由以下几个关键组件构成:

mermaid

核心组件详解

1. DevTools Page - 入口控制器

DevTools Page是扩展的入口点,负责创建和管理DevTools面板:

// pages/devtools/src/index.ts
try {
  console.log("Edit 'pages/devtools/src/index.ts' and save to reload.");
  chrome.devtools.panels.create('Dev Tools', '/icon-34.png', '/devtools-panel/index.html');
} catch (e) {
  console.error(e);
}

这个简单的代码片段展示了如何使用Chrome DevTools API创建面板。chrome.devtools.panels.create()方法接受三个参数:

  • 面板名称
  • 图标路径
  • 面板HTML页面路径
2. DevTools Panel - 面板主体

Panel组件是DevTools扩展的核心界面,采用React + TypeScript构建:

// pages/devtools-panel/src/Panel.tsx
const Panel = () => {
  const { isLight } = useStorage(exampleThemeStorage);
  const logo = isLight ? 'devtools-panel/logo_horizontal.svg' : 'devtools-panel/logo_horizontal_dark.svg';

  const goGithubSite = () => chrome.tabs.create(PROJECT_URL_OBJECT);

  return (
    <div className={cn('App', isLight ? 'bg-slate-50' : 'bg-gray-800')}>
      <header className={cn('App-header', isLight ? 'text-gray-900' : 'text-gray-100')}>
        <button onClick={goGithubSite}>
          <img src={chrome.runtime.getURL(logo)} className="App-logo" alt="logo" />
        </button>
        <p>
          Edit <code>pages/devtools-panel/src/Panel.tsx</code>
        </p>
        <ToggleButton onClick={exampleThemeStorage.toggle}>{t('toggleTheme')}</ToggleButton>
      </header>
    </div>
  );
};
3. 构建配置系统

Vite配置确保了开发和生产环境的一致性:

// pages/devtools-panel/vite.config.mts
export default withPageConfig({
  resolve: {
    alias: {
      '@src': srcDir,
    },
  },
  publicDir: resolve(rootDir, 'public'),
  build: {
    outDir: resolve(rootDir, '..', '..', 'dist', 'devtools-panel'),
  },
});

架构特性分析

模块化设计
模块类型功能描述位置
核心面板主要的用户界面pages/devtools-panel/src/
入口控制器面板创建和管理pages/devtools/src/
共享工具包通用功能和组件packages/ 目录
构建配置编译和打包配置vite.config.mts
依赖管理架构

mermaid

构建输出结构

构建过程生成以下目录结构:

dist/
├── devtools-panel/
│   ├── index.html
│   ├── assets/
│   │   ├── Panel.[hash].js
│   │   └── index.[hash].js
│   └── manifest.json (引用)
└── devtools/
    └── index.html

技术栈优势

这个架构采用了现代化的技术栈组合:

  1. React + TypeScript: 提供类型安全的组件开发
  2. Vite: 快速的构建工具和开发服务器
  3. Tailwind CSS: 实用优先的CSS框架
  4. Turborepo: 多包管理工具
  5. Chrome Extension MV3: 最新的扩展规范

扩展性设计

架构设计考虑了扩展性,开发者可以:

  1. 添加新面板: 通过修改DevTools Page创建多个面板
  2. 自定义功能: 利用共享包快速集成新功能
  3. 主题定制: 通过存储管理系统实现主题切换
  4. 国际化: 使用i18n包支持多语言

这种架构设计确保了DevTools扩展的稳定性、可维护性和可扩展性,为开发者提供了一个强大的基础框架来构建复杂的浏览器开发者工具扩展。

自定义DevTools面板开发

在Chrome扩展开发中,DevTools面板是开发者工具的重要组成部分,它允许开发者创建自定义的调试和分析工具。本小节将详细介绍如何使用React + Vite + TypeScript技术栈来开发自定义的DevTools面板。

DevTools面板架构概述

自定义DevTools面板的开发涉及两个主要组件:

mermaid

核心实现代码分析

1. DevTools页面入口

DevTools页面负责创建自定义面板,这是整个流程的起点:

// pages/devtools/src/index.ts
try {
  console.log("Edit 'pages/devtools/src/index.ts' and save to reload.");
  chrome.devtools.panels.create(
    'Dev Tools',           // 面板标题
    '/icon-34.png',        // 面板图标
    '/devtools-panel/index.html'  // 面板页面路径
  );
} catch (e) {
  console.error(e);
}
2. 面板React组件

面板的核心功能通过React组件实现,支持主题切换和国际化:

// pages/devtools-panel/src/Panel.tsx
import { t } from '@extension/i18n';
import { useStorage, withErrorBoundary, withSuspense } from '@extension/shared';
import { exampleThemeStorage } from '@extension/storage';

const Panel = () => {
  const { isLight } = useStorage(exampleThemeStorage);
  const logo = isLight ? 'devtools-panel/logo_horizontal.svg' : 'devtools-panel/logo_horizontal_dark.svg';

  const goGithubSite = () => chrome.tabs.create(PROJECT_URL_OBJECT);

  return (
    <div className={cn('App', isLight ? 'bg-slate-50' : 'bg-gray-800')}>
      <header className={cn('App-header', isLight ? 'text-gray-900' : 'text-gray-100')}>
        <button onClick={goGithubSite}>
          <img src={chrome.runtime.getURL(logo)} className="App-logo" alt="logo" />
        </button>
        <p>Edit <code>pages/devtools-panel/src/Panel.tsx</code></p>
        <ToggleButton onClick={exampleThemeStorage.toggle}>{t('toggleTheme')}</ToggleButton>
      </header>
    </div>
  );
};

关键配置说明

Manifest配置

在manifest.ts中需要正确配置devtools_page:

// chrome-extension/manifest.ts
devtools_page: 'devtools/index.html',
web_accessible_resources: [
  {
    resources: ['*.js', '*.css', '*.svg', 'icon-128.png', 'icon-34.png'],
    matches: ['*://*/*'],
  },
],
Vite配置

面板使用独立的Vite配置进行构建:

// pages/devtools-panel/vite.config.mts
import { defineConfig } from 'vite';
import { chromeExtension } from 'rollup-plugin-chrome-extension';

export default defineConfig({
  plugins: [chromeExtension()],
  build: {
    rollupOptions: {
      input: 'index.html',
    },
  },
});

功能特性表格

功能特性实现方式说明
主题切换useStorage + Tailwind CSS支持明暗主题自动切换
国际化@extension/i18n包多语言支持
错误边界withErrorBoundary HOC组件级错误处理
加载状态withSuspense HOC异步加载状态管理
资源访问chrome.runtime.getURL安全访问扩展资源

开发最佳实践

1. 面板设计原则

mermaid

2. 性能优化建议
  • 使用React.memo避免不必要的重渲染
  • 实现虚拟滚动处理大量数据
  • 使用Web Worker处理复杂计算
  • 合理使用Chrome DevTools API
3. 调试技巧
// 在面板中启用详细日志
console.log('面板加载完成');
chrome.devtools.inspectedWindow.eval(
  'console.log("当前页面:", document.title)',
  (result, isException) => {
    if (isException) console.error('执行出错:', isException);
  }
);

实际应用场景

自定义DevTools面板适用于多种场景:

  1. 性能监控:实时显示页面性能指标
  2. 状态调试:可视化React/Vue组件状态
  3. 网络分析:自定义网络请求监控
  4. SEO检查:页面SEO相关指标分析
  5. 无障碍检测:网页可访问性检查工具

通过这个模板,开发者可以快速构建功能丰富、性能优异的自定义DevTools面板,充分利用Chrome扩展和现代前端技术的优势。

与页面元素的交互通信

在Chrome扩展开发中,DevTools Panel与页面元素的交互通信是一个核心功能。通过这种通信机制,我们可以实现实时监控页面状态、修改DOM元素、获取页面数据等强大功能。本文将深入探讨如何在React + Vite + TypeScript的技术栈中实现DevTools Panel与页面内容脚本之间的高效通信。

通信架构概览

DevTools Panel与页面元素的交互主要依赖于Chrome扩展API提供的消息传递机制。整个通信流程可以分为以下几个关键组件:

mermaid

消息传递机制

1. 从DevTools Panel发送消息

在DevTools Panel的React组件中,我们可以使用chrome.runtime.sendMessage方法向后台脚本发送消息:

// pages/devtools-panel/src/components/Inspector.tsx
import { useState } from 'react';

const Inspector = () => {
  const [pageData, setPageData] = useState<any>(null);

  const inspectElement = async (selector: string) => {
    try {
      const response = await chrome.runtime.sendMessage({
        type: 'INSPECT_ELEMENT',
        payload: { selector }
      });
      
      setPageData(response.data);
    } catch (error) {
      console.error('Inspection failed:', error);
    }
  };

  return (
    <div className="inspector-panel">
      <button onClick={() => inspectElement('body')}>
        检查页面Body
      </button>
      {pageData && (
        <pre>{JSON.stringify(pageData, null, 2)}</pre>
      )}
    </div>
  );
};
2. 后台脚本的消息路由

后台脚本作为消息中转站,负责将消息路由到正确的标签页:

// chrome-extension/src/background/index.ts
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'INSPECT_ELEMENT') {
    // 获取当前激活的标签页
    chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
      if (tabs[0]?.id) {
        chrome.tabs.sendMessage(
          tabs[0].id,
          message,
          (response) => {
            sendResponse(response);
          }
        );
      }
    });
    return true; // 保持消息通道开放
  }
});
3. 内容脚本处理页面交互

内容脚本运行在页面上下文中,可以直接操作DOM:

// pages/content/src/element-inspector.ts
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'INSPECT_ELEMENT') {
    const { selector } = message.payload;
    
    try {
      const element = document.querySelector(selector);
      if (!element) {
        sendResponse({ error: 'Element not found' });
        return;
      }

      const elementData = {
        tagName: element.tagName,
        className: element.className,
        id: element.id,
        textContent: element.textContent?.slice(0, 100),
        boundingRect: element.getBoundingClientRect(),
        computedStyle: getComputedStyle(element),
        attributes: Array.from(element.attributes).reduce((acc, attr) => {
          acc[attr.name] = attr.value;
          return acc;
        }, {} as Record<string, string>)
      };

      sendResponse({ data: elementData });
    } catch (error) {
      sendResponse({ error: error.message });
    }
  }
});

双向通信模式

为了实现更复杂的交互,我们需要建立双向通信通道:

mermaid

高级交互功能

实时DOM监控
// 实时监控DOM变化的实现
class DOMObserver {
  private observer: MutationObserver;
  private callback: (mutations: MutationRecord[]) => void;

  constructor(callback: (mutations: MutationRecord[]) => void) {
    this.callback = callback;
    this.observer = new MutationObserver(this.handleMutations.bind(this));
  }

  start() {
    this.observer.observe(document.body, {
      childList: true,
      subtree: true,
      attributes: true,
      characterData: true
    });
  }

  stop() {
    this.observer.disconnect();
  }

  private handleMutations(mutations: MutationRecord[]) {
    const simplifiedMutations = mutations.map(mutation => ({
      type: mutation.type,
      target: this.getElementInfo(mutation.target as Element),
      addedNodes: Array.from(mutation.addedNodes).map(node => 
        this.getElementInfo(node as Element)
      ),
      removedNodes: Array.from(mutation.removedNodes).map(node => 
        this.getElementInfo(node as Element)
      ),
      attributeName: mutation.attributeName,
      oldValue: mutation.oldValue
    }));

    chrome.runtime.sendMessage({
      type: 'DOM_MUTATION',
      payload: simplifiedMutations
    });
  }

  private getElementInfo(element: Element) {
    return {
      tagName: element.tagName,
      id: element.id,
      className: element.className
    };
  }
}
性能数据收集
// 性能监控功能
class PerformanceMonitor {
  static collectPerformanceData() {
    const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
    const resources = performance.getEntriesByType('resource');
    
    return {
      navigation: {
        dns: navigation.domainLookupEnd - navigation.domainLookupStart,
        tcp: navigation.connectEnd - navigation.connectStart,
        request: navigation.responseStart - navigation.requestStart,
        response: navigation.responseEnd - navigation.responseStart,
        domComplete: navigation.domComplete,
        loadEvent: navigation.loadEventEnd
      },
      resources: resources.map(resource => ({
        name: resource.name,
        duration: resource.duration,
        transferSize: (resource as PerformanceResourceTiming).transferSize
      }))
    };
  }
}

// 在内容脚本中暴露性能数据接口
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'GET_PERFORMANCE_DATA') {
    const performanceData = PerformanceMonitor.collectPerformanceData();
    sendResponse({ data: performanceData });
  }
});

错误处理与调试

在通信过程中,健全的错误处理机制至关重要:

// 增强的错误处理装饰器
function withErrorHandling<T extends (...args: any[]) => any>(
  fn: T,
  context: string
): (...args: Parameters<T>) => ReturnType<T> | { error: string } {
  return (...args: Parameters<T>) => {
    try {
      return fn(...args);
    } catch (error) {
      console.error(`Error in ${context}:`, error);
      return { error: error.message };
    }
  };
}

// 使用示例
const safeSendMessage = withErrorHandling(
  chrome.runtime.sendMessage,
  'DevTools Panel Communication'
);

通信性能优化

为了确保通信的高效性,我们可以采用以下优化策略:

优化策略实施方法效果
消息批处理将多个小消息合并为一个大消息减少通信次数,提高效率
数据压缩对传输的数据进行压缩减少数据传输量
缓存机制缓存频繁请求的数据避免重复计算和传输
懒加载按需请求数据减少初始加载时间
// 消息批处理实现
class MessageBatcher {
  private batch: any[] = [];
  private batchTimeout: NodeJS.Timeout | null = null;
  private readonly BATCH_DELAY = 50;

  addMessage(message: any) {
    this.batch.push(message);
    
    if (!this.batchTimeout) {
      this.batchTimeout = setTimeout(() => {
        this.flushBatch();
      }, this.BATCH_DELAY);
    }
  }

  private flushBatch() {
    if (this.batch.length > 0) {
      chrome.runtime.sendMessage({
        type: 'BATCH_MESSAGE',
        payload: this.batch
      });
      this.batch = [];
    }
    
    if (this.batchTimeout) {
      clearTimeout(this.batchTimeout);
      this.batchTimeout = null;
    }
  }
}

通过上述技术方案,我们可以构建出高效、可靠的DevTools Panel与页面元素交互通信系统。这种通信模式不仅适用于元素检查,还可以扩展到性能监控、网络请求拦截、用户行为分析等多个领域,为开发者提供强大的网页调试和分析能力。

性能监控与调试技巧

在开发Chrome扩展的DevTools Panel时,性能监控和调试是确保扩展稳定运行的关键环节。本节将深入探讨如何利用现代工具和技术来优化扩展性能,并提供实用的调试技巧。

性能监控策略

实时性能指标收集

在DevTools Panel中实现性能监控,首先需要建立一套完整的指标收集系统。以下是一个性能监控组件的实现示例:

interface PerformanceMetrics {
  memoryUsage: number;
  cpuUsage: number;
  executionTime: number;
  domContentLoaded: number;
  loadTime: number;
  networkRequests: number;
}

class PerformanceMonitor {
  private metrics: PerformanceMetrics;
  private startTime: number;

  constructor() {
    this.metrics = {
      memoryUsage: 0,
      cpuUsage: 0,
      executionTime: 0,
      domContentLoaded: 0,
      loadTime: 0,
      networkRequests: 0
    };
    this.startTime = performance.now();
  }

  startMonitoring() {
    // 监听性能相关事件
    window.addEventListener('load', this.onLoad.bind(this));
    window.addEventListener('DOMContentLoaded', this.onDOMContentLoaded.bind(this));
    
    // 定期收集性能数据
    setInterval(() => this.collectMetrics(), 1000);
  }

  private collectMetrics() {
    if (performance.memory) {
      this.metrics.memoryUsage = performance.memory.usedJSHeapSize;
    }
    
    this.metrics.executionTime = performance.now() - this.startTime;
    this.metrics.networkRequests = performance.getEntriesByType('resource').length;
  }

  private onLoad() {
    this.metrics.loadTime = performance.now() - this.startTime;
  }

  private onDOMContentLoaded() {
    this.metrics.domContentLoaded = performance.now() - this.startTime;
  }

  getMetrics(): PerformanceMetrics {
    return { ...this.metrics };
  }
}
性能数据可视化

为了在DevTools Panel中直观展示性能数据,我们可以创建一个实时监控面板:

const PerformancePanel: React.FC = () => {
  const [metrics, setMetrics] = useState<PerformanceMetrics>({
    memoryUsage: 0,
    cpuUsage: 0,
    executionTime: 0,
    domContentLoaded: 0,
    loadTime: 0,
    networkRequests: 0
  });

  useEffect(() => {
    const monitor = new PerformanceMonitor();
    monitor.startMonitoring();
    
    const interval = setInterval(() => {
      setMetrics(monitor.getMetrics());
    }, 1000);

    return () => clearInterval(interval);
  }, []);

  return (
    <div className="performance-panel">
      <h3>实时性能监控</h3>
      <div className="metrics-grid">
        <MetricCard 
          title="内存使用" 
          value={`${(metrics.memoryUsage / 1024 / 1024).toFixed(2)} MB`}
          trend="stable"
        />
        <MetricCard 
          title="执行时间" 
          value={`${metrics.executionTime.toFixed(2)} ms`}
          trend="decreasing"
        />
        <MetricCard 
          title="网络请求" 
          value={metrics.networkRequests.toString()}
          trend="increasing"
        />
      </div>
    </div>
  );
};

调试技巧与最佳实践

Chrome DevTools集成调试

利用Chrome DevTools的强大功能进行扩展调试:

// 自定义控制台输出,便于调试
class DebugLogger {
  private static instance: DebugLogger;
  private enabled: boolean;

  private constructor() {
    this.enabled = process.env.NODE_ENV === 'development';
  }

  static getInstance(): DebugLogger {
    if (!DebugLogger.instance) {
      DebugLogger.instance = new DebugLogger();
    }
    return DebugLogger.instance;
  }

  log(message: string, data?: any) {
    if (this.enabled) {
      console.log(`[DevTools Panel] ${message}`, data || '');
    }
  }

  error(message: string, error?: Error) {
    if (this.enabled) {
      console.error(`[DevTools Panel Error] ${message}`, error || '');
    }
  }

  warn(message: string, data?: any) {
    if (this.enabled) {
      console.warn(`[DevTools Panel Warning] ${message}`, data || '');
    }
  }
}

// 使用示例
const logger = DebugLogger.getInstance();
logger.log('组件初始化完成');
性能瓶颈分析

使用Performance API进行详细的性能分析:

const measurePerformance = (taskName: string, task: () => void) => {
  const startMark = `${taskName}-start`;
  const endMark = `${taskName}-end`;
  
  performance.mark(startMark);
  
  try {
    task();
  } finally {
    performance.mark(endMark);
    performance.measure(taskName, startMark, endMark);
    
    const measures = performance.getEntriesByName(taskName);
    const duration = measures[measures.length - 1]?.duration || 0;
    
    logger.log(`任务 ${taskName} 执行时间: ${duration.toFixed(2)}ms`);
  }
};

// 使用示例
measurePerformance('数据加载', () => {
  // 执行耗时的数据加载操作
  loadHeavyData();
});

内存泄漏检测与预防

内存泄漏检测模式
class MemoryLeakDetector {
  private references = new WeakMap<object, string>();
  private leakCount = 0;

  track(object: any, context: string = 'unknown') {
    if (typeof object === 'object' && object !== null) {
      this.references.set(object, context);
    }
  }

  checkLeaks() {
    const entries = Array.from(this.references.entries());
    const currentTime = Date.now();
    
    entries.forEach(([obj, context]) => {
      // 检查对象是否仍然被引用
      if (!this.isStillReferenced(obj)) {
        this.leakCount++;
        logger.warn(`检测到潜在内存泄漏: ${context}`);
      }
    });
  }

  private isStillReferenced(obj: any): boolean {
    // 实现引用检查逻辑
    return true; // 简化实现
  }

  getLeakCount(): number {
    return this.leakCount;
  }
}

性能优化策略表

以下表格总结了关键的性能优化策略:

优化领域具体策略预期效果实施难度
内存管理使用WeakMap存储临时引用减少内存泄漏风险中等
渲染优化虚拟化长列表提高渲染性能
网络请求请求合并与缓存减少网络开销中等
代码分割动态导入模块降低初始加载时间中等
数据存储使用IndexedDB替代localStorage提高存储性能

调试工作流程图

以下流程图展示了完整的调试流程:

mermaid

实时性能仪表板实现

创建一个综合性的性能监控仪表板:

const PerformanceDashboard: React.FC = () => {
  const [realTimeMetrics, setRealTimeMetrics] = useState<PerformanceMetrics>();
  const [historicalData, setHistoricalData] = useState<PerformanceMetrics[]>([]);

  useEffect(() => {
    const monitor = new PerformanceMonitor();
    monitor.startMonitoring();

    const interval = setInterval(() => {
      const metrics = monitor.getMetrics();
      setRealTimeMetrics(metrics);
      setHistoricalData(prev => [...prev.slice(-59), metrics]);
    }, 1000);

    return () => clearInterval(interval);
  }, []);

  return (
    <div className="performance-dashboard">
      <h2>性能监控仪表板</h2>
      
      <div className="real-time-metrics">
        <h3>实时指标</h3>
        <div className="metric-cards">
          <div className="metric-card">
            <span className="label">内存使用:</span>
            <span className="value">
              {realTimeMetrics ? (realTimeMetrics.memoryUsage / 1024 / 1024).toFixed(2) : '0'} MB
            </span>
          </div>
          {/* 其他指标卡片 */}
        </div>
      </div>

      <div className="historical-charts">
        <h3>历史趋势</h3>
        <PerformanceChart data={historicalData} />
      </div>

      <div className="performance-tips">
        <h3>优化建议</h3>
        <ul>
          <li>定期清理未使用的对象引用</li>
          <li>使用Web Worker处理计算密集型任务</li>
          <li>实现请求缓存和合并策略</li>
          <li>监控第三方库的性能影响</li>
        </ul>
      </div>
    </div>
  );
};

通过上述技术和策略,开发者可以构建出高性能、易调试的Chrome扩展DevTools Panel,确保扩展在各种使用场景下都能提供流畅的用户体验。

总结

本文全面系统地介绍了Chrome扩展DevTools Panel的开发全流程,从架构设计到具体实现,从基础功能到高级特性。通过模块化的架构设计、现代化的技术栈选择、高效的通信机制和完善的性能监控体系,开发者可以构建出功能强大、性能优异、易于维护的开发者工具扩展。文章提供的代码示例、优化策略和调试技巧具有很高的实用价值,为开发者提供了从入门到精通的完整指导,是DevTools扩展开发的重要参考资源。

【免费下载链接】chrome-extension-boilerplate-react-vite Chrome Extension Boilerplate with React + Vite + Typescript 【免费下载链接】chrome-extension-boilerplate-react-vite 项目地址: https://gitcode.com/GitHub_Trending/ch/chrome-extension-boilerplate-react-vite

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值