基于大模型的组件无障碍合规修复:从检测报告到代码补丁

cover

一、无障碍合规的"最后一公里":从检测到修复的工程鸿沟

前端无障碍(Accessibility, a11y)合规检测工具(如 axe-core、Lighthouse)已经相当成熟,能够自动识别 WCAG 违规项:缺少 alt 属性的图片、没有 aria-label 的按钮、键盘无法聚焦的交互元素。但检测只是第一步,从检测报告到代码修复之间存在巨大的工程鸿沟。

一份典型的无障碍检测报告可能包含 50-100 个违规项,每个违规项需要开发者理解 WCAG 规范、确定修复方案、修改代码并验证。对于不熟悉无障碍规范的开发者,这个过程耗时且容易出错。基于大模型的组件无障碍合规修复,核心思路是:将检测报告和组件源码输入大模型,自动生成修复补丁,将修复时间从小时级压缩到分钟级。

二、AI 无障碍修复的架构与流程

flowchart TD
    A[组件源码] --> B[axe-core 无障碍检测]
    B --> C[生成违规报告]
    C --> D[大模型分析: 违规项 + 源码上下文]
    D --> E[生成修复补丁]
    E --> F[应用补丁到源码]
    F --> G[重新检测验证]
    G --> H{违规项是否清零?}
    H -->|是| I[修复完成]
    H -->|否| J[迭代修复: 重新分析]
    J --> D

三、AI 无障碍修复的代码实现

3.1 无障碍检测与报告生成

import Axe from 'axe-core';

interface AccessibilityViolation {
  id: string;           // 违规规则 ID
  impact: 'critical' | 'serious' | 'moderate' | 'minor';
  description: string;
  target: string[];     // CSS 选择器
  html: string;         // 违规元素的 HTML
  fixSuggestions: string;
}

class AccessibilityScanner {
  /**
   * 扫描组件的无障碍违规项
   * 返回结构化的违规报告
   */
  async scanComponent(container: HTMLElement): Promise<AccessibilityViolation[]> {
    const results = await Axe.run(container, {
      runOnly: {
        type: 'tag',
        values: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']
      }
    });

    return results.violations.map(v => ({
      id: v.id,
      impact: v.impact as AccessibilityViolation['impact'],
      description: v.description,
      target: v.nodes.map(n => n.target.join(' > ')),
      html: v.nodes.map(n => n.html).join('\n'),
      fixSuggestions: this.generateFixSuggestion(v.id)
    }));
  }

  private generateFixSuggestion(ruleId: string): string {
    const suggestions: Record<string, string> = {
      'image-alt': '为 <img> 添加 alt 属性,描述图片内容',
      'button-name': '为 <button> 添加可见文本或 aria-label',
      'label': '为表单控件关联 <label> 或添加 aria-label',
      'color-contrast': '提高文本与背景的对比度至 4.5:1 以上',
      'keyboard': '确保交互元素可通过 Tab 键聚焦',
      'aria-roles': '修正 ARIA role 属性的使用',
    };
    return suggestions[ruleId] ?? '参考 WCAG 规范修复';
  }
}

3.2 AI 修复补丁生成

interface FixPatch {
  filePath: string;
  originalCode: string;
  fixedCode: string;
  explanation: string;
}

class A11yFixGenerator {
  private llmClient: LLMClient;

  /**
   * 基于违规报告和组件源码生成修复补丁
   * Prompt 包含违规详情和修复指导,确保补丁符合 WCAG 规范
   */
  async generateFix(
    sourceCode: string,
    violations: AccessibilityViolation[]
  ): Promise<FixPatch> {
    const prompt = this.buildPrompt(sourceCode, violations);
    const response = await this.llmClient.chat(prompt);
    return this.parsePatch(response, sourceCode);
  }

  private buildPrompt(sourceCode: string, violations: AccessibilityViolation[]): string {
    const violationSummary = violations
      .map(v => `- [${v.impact}] ${v.id}: ${v.description}\n  目标: ${v.target.join(', ')}\n  修复建议: ${v.fixSuggestions}`)
      .join('\n');

    return `
你是前端无障碍修复专家。请根据以下违规报告修复组件代码。

## 组件源码
\`\`\`tsx
${sourceCode}
\`\`\`

## 无障碍违规项
${violationSummary}

## 修复要求
1. 修复所有违规项,不改变组件的功能和视觉表现
2. 优先使用语义化 HTML(如 <button> 而非 <div onClick>)
3. 添加必要的 ARIA 属性(aria-label, aria-describedby, role)
4. 确保键盘可访问性(tabIndex, onKeyDown)
5. 保持代码风格一致

请输出修复后的完整组件代码。
`;
  }

  private parsePatch(response: string, originalCode: string): FixPatch {
    // 从 LLM 响应中提取代码块
    const codeMatch = response.match(/```tsx\n([\s\S]*?)```/);
    const fixedCode = codeMatch ? codeMatch[1] : originalCode;

    return {
      filePath: '',
      originalCode,
      fixedCode,
      explanation: response.replace(/```[\s\S]*?```/g, '').trim()
    };
  }
}

3.3 修复验证与迭代

class A11yFixPipeline {
  private scanner: AccessibilityScanner;
  private fixer: A11yFixGenerator;
  private maxIterations: number = 3;

  /**
   * 完整的修复流水线:检测 → 修复 → 验证 → 迭代
   */
  async fixComponent(
    container: HTMLElement,
    sourceCode: string
  ): Promise<{ fixedCode: string; remainingViolations: number }> {
    let currentCode = sourceCode;
    let violations = await this.scanner.scanComponent(container);

    for (let i = 0; i < this.maxIterations && violations.length > 0; i++) {
      // 生成修复补丁
      const patch = await this.fixer.generateFix(currentCode, violations);
      currentCode = patch.fixedCode;

      // 重新渲染组件并检测
      container.innerHTML = this.renderComponent(currentCode);
      violations = await this.scanner.scanComponent(container);

      if (violations.length === 0) {
        return { fixedCode: currentCode, remainingViolations: 0 };
      }
    }

    return { fixedCode: currentCode, remainingViolations: violations.length };
  }

  private renderComponent(code: string): string {
    // 简化:直接将代码作为 HTML 渲染
    // 生产环境应使用实际的组件渲染器
    return code;
  }
}

四、AI 无障碍修复的边界分析与架构权衡

AI 修复的语义准确性。AI 可以为按钮添加 aria-label,但标签文本是否准确描述按钮功能,仍需人工审核。例如,一个关闭按钮的 aria-label 应该是"关闭对话框"而非"X"。

修复引入的新违规。AI 修复一个违规项时,可能引入新的违规。例如,添加 role="button" 但忘记添加 tabIndex={0},导致键盘仍无法聚焦。迭代修复机制可以捕获这类问题,但增加了修复轮次。

动态内容的无障碍挑战。AI 修复主要针对静态 HTML 结构。对于动态内容(如 AJAX 加载的列表、实时更新的通知),无障碍支持需要在 JavaScript 逻辑中添加 aria-live 区域,这超出了纯代码补丁的范围。

适用边界:AI 无障碍修复最适合静态组件的批量合规修复。对于复杂的交互组件(如拖拽排序、虚拟滚动),无障碍支持需要深度定制,AI 生成的补丁只能作为参考。

五、总结

基于大模型的组件无障碍合规修复通过"检测 → AI 修复 → 验证 → 迭代"的流水线,将修复时间从小时级压缩到分钟级。落地时需关注 AI 修复的语义准确性审核、修复引入新违规的迭代处理、以及动态内容的无障碍挑战。建议将 AI 修复作为初筛工具,对关键组件仍需人工审核修复质量。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐