Files
App.K12.Student.Miniapp_Flu…/docs/demos/rich_text_demo.md
T
2026-09-04 15:22:20 +08:00

5.2 KiB
Raw Blame History

【Demo】富文本渲染(RichTextContent:HTML → 题目/解析)

源文件:lib/pages/demo/rich_text_demo.dart | 分类:组件封装 一句话:后端给的 HTML(题目/解析/听力材料)渲染成富文本 —— 还支持字号调节、深色模式、自定义样式。


一、这个 Demo 解决什么问题

教育 App 的题目/解析是后端返回的 HTML 字符串(<h3>、<img>、<table>、<audio>…), 前端要把它渲染成漂亮的可交互富文本。本页演示 RichTextContent(lib/core/components/rich_text_content.dart):

  • 完整渲染:标题、粗体、上标下标、<ul> 选项、<table> 表格;
  • <audio> 标签自动替换成本项目的 AudioPlayerCard;
  • 学生端能力:字号 Aa± 实时调节、深色模式切换(通过 textStyle 注入);
  • 自定义样式:.answer 类 → 红色加粗(customStylesBuilder)。

对照 Vue:相当于 v-html + 样式定制,但 Flutter 没有浏览器引擎,得用 flutter_html 这类包逐标签渲染。


二、怎么运行 / 演示内容

runApp(const MaterialApp(home: RichTextDemoPage()));
  • 顶部控制栏:滑块拖字号(12~24)、切深色开关 → 正文实时变化;
  • 页面里:化学方程式上下标、本地图片(asset:// 协议)、表格、听力音频、视频、红色加粗的答案 B。

三、核心知识点(新手向)

1. 一行渲染 HTML

RichTextContent(
  html: _html,   // 后端返回的 HTML 字符串
  // 字号 + 深色模式都通过 textStyle 注入(学生端统一入口)
  textStyle: TextStyle(
    fontSize: _fontSize,
    height: 1.6,
    color: _dark ? Colors.white70 : const Color(0xFF333333),
  ),
)

textStyle 注入是核心设计:页面不重建 HTML、不改渲染器,只改一个 TextStyle 就全局换字号/颜色——学生端"无障碍调节"的标准做法。

2. 自定义样式:customStylesBuilder

customStylesBuilder: (element) {
  if (element.className.contains('answer')) {
    return const {'color': '#EF4444', 'font-weight': 'bold'};
  }
  return null;  // 其他元素不特殊处理
},

后端 HTML 里写 <span class="answer">B</span> → 渲染成红色加粗。按 class 定制样式,不用后端改结构。

3. 本地图片:asset:// 协议

<img src="asset://assets/img/luola_logo.png" width="160" height="160"/>

asset:// + 资源路径 = 加载本地资源图片(最稳,不受网络影响)。实际后端返回完整 URL 或相对路径(相对路径需业务层拼 baseUrl)。

4. audio 标签自动替换

<audio src="asset://assets/audio/sample-30s.mp3" title="听力材料(30 秒)" controls></audio>

组件内部检测 <audio> → 替换成 AudioPlayerCard(见 audio_player_demo.md)——听力题直接能播。


四、关键代码剖析

static const String _html = '''
<h3>第 1 题 · 阅读理解</h3>
<p>光合作用是植物利用光能将<strong>二氧化碳</strong>和<strong>水</strong>合成有机物并释放氧气的过程。化学方程式:6CO<sub>2</sub> + 6H<sub>2</sub>O → C<sub>6</sub>H<sub>12</sub>O<sub>6</sub> + 6O<sub>2</sub>,面积用 x<sup>2</sup> 表示。</p>
<img src="asset://assets/img/luola_logo.png" width="160" height="160"/>
<ul>
  <li>A. 光合作用只发生在夜间</li>
  <li>B. 光合作用会释放氧气</li>
</ul>
<table border="1" cellpadding="6">
  <tr><th>条件</th><th>有光</th><th>黑暗</th></tr>
  <tr><td>释放 O₂</td><td>√</td><td>×</td></tr>
</table>
<audio src="asset://assets/audio/sample-30s.mp3" title="听力材料(30 秒)" controls></audio>
<p><b>【解析】</b>正确答案:<span class="answer">B</span>。</p>
''';

字号控制条(12~24 共 12 档):

Slider(
  value: _fontSize,
  min: 12,
  max: 24,
  divisions: 12,                          // 档位数 → 滑块只能停在整数
  activeColor: const Color(0xFF3B82F6),
  onChanged: (v) => setState(() => _fontSize = v),
)

五、踩坑记录

  1. textStyle 只影响文本:图片、表格、AudioPlayerCard 是各自样式——深色模式下卡片白底不随主题变(业务侧需按需适配);
  2. 内联 CSS 只支持常用属性,复杂布局会降级(flutter_html 的边界,别拿它当浏览器);
  3. 公式(LaTeX)本 demo 未含:以后在 RichTextContent 的 customWidgetBuilder 里扩展(如 flutter_math_fork);
  4. 相对路径图片要业务层拼 baseUrl,别把裸相对路径直接塞进 html;
  5. 深色模式只是 _dark 布尔 + 换色,页面底色/AppBar 也要跟着换(本页手动处理了,主题化见 theme_demo.md)。

六、扩展思考

  • <audio> 替换成的播放器组件见 audio_player_demo.md;
  • 富文本在真实考试页的运用见 exam_demo.md(题干 HTML 渲染);
  • 学生端"字号调节 + 深色模式"这类偏好设置,进阶做法是存进全局状态(Riverpod),见 exam_architecture.md 的 Riverpod 章节;
  • 真实项目:_html 换成接口返回的题目 HTML,asset:// 换成 CDN 完整 URL 即可。