Files
App.K12.Student.Miniapp_Flu…/lib/pages/demo/rich_text_demo.dart
T
2026-09-04 15:22:20 +08:00

149 lines
5.7 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import 'package:new_learn/core/components/rich_text_content.dart';
/// ============================================================
/// 【RichTextDemoPage】富文本渲染 Demo
///
/// 一、本页演示什么:
/// 1. 题目/解析 HTML 完整渲染:标题、粗体、图片、上标下标、
/// <ul> 选项、<table> 表格;
/// 2. <audio> 标签自动替换成本项目 AudioPlayerCard;
/// 3. 学生端能力:字号 Aa± 实时调节、深色模式切换(textStyle 注入);
/// 4. 自定义样式:.answer 类 → 红色加粗(customStylesBuilder)。
///
/// 二、图片说明:
/// 用 asset:// 协议加载本地图片(最稳),实际后端返回的是
/// 完整 URL 或相对路径(相对路径需业务层拼 baseUrl)。
///
/// 三、高频坑:
/// 1. textStyle 只影响文本,图片/表格/AudioPlayerCard 是各自样式,
/// 深色模式下卡片白底不随主题变(业务侧需按需适配);
/// 2. 内联 CSS 只支持常用属性,复杂布局会降级;
/// 3. 以后接公式(LaTeX)在 RichTextContent 的 customWidgetBuilder
/// 里扩展,本 demo 未含公式。
/// ============================================================
class RichTextDemoPage extends StatefulWidget {
const RichTextDemoPage({super.key});
@override
State<RichTextDemoPage> createState() => _RichTextDemoPageState();
}
class _RichTextDemoPageState extends State<RichTextDemoPage> {
/// 字号(12 ~ 24,学生端可调)
double _fontSize = 15;
/// 深色模式演示开关
bool _dark = false;
/// 模拟后端返回的题目 HTML(含图片/上标/下标/表格/音频/解析)
static const String _html = '''
<h3>第 1 题 · 阅读理解</h3>
<p>阅读下面的材料,完成题目。</p>
<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"/>
<p>根据材料,下列说法正确的是:</p>
<ul>
<li>A. 光合作用只发生在夜间</li>
<li>B. 光合作用会释放氧气</li>
<li>C. 光合作用会消耗氧气</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>
<video src="https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4" title="实验视频" controls></video>
<p><b>【解析】</b>正确答案:<span class="answer">B</span>。光合作用光反应阶段必须在有光的条件下进行,并释放氧气。</p>
''';
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: _dark ? const Color(0xFF1E1E1E) : Colors.white,
appBar: AppBar(
title: const Text('富文本渲染'),
centerTitle: true,
backgroundColor: _dark ? const Color(0xFF262626) : null,
foregroundColor: _dark ? Colors.white : null,
),
body: Column(
children: [
_buildControlBar(),
const Divider(height: 1),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: RichTextContent(
html: _html,
// 字号 + 深色模式都通过 textStyle 注入(学生端统一入口)
textStyle: TextStyle(
fontSize: _fontSize,
height: 1.6,
color: _dark
? Colors.white70
: const Color(0xFF333333),
),
// 自定义样式:.answer 类 → 红色加粗
customStylesBuilder: (element) {
if (element.className.contains('answer')) {
return const {'color': '#EF4444', 'font-weight': 'bold'};
}
return null;
},
),
),
),
],
),
);
}
/// 顶部控制栏:字号 Aa- / Aa+ + 深色开关
Widget _buildControlBar() {
return Container(
color: _dark ? const Color(0xFF262626) : const Color(0xFFF7F8FA),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Row(
children: [
IconButton(
icon: const Icon(Icons.text_decrease, size: 20),
color: _dark ? Colors.white : null,
onPressed: () => setState(() {
if (_fontSize > 12) _fontSize -= 1;
}),
),
Expanded(
child: Slider(
value: _fontSize,
min: 12,
max: 24,
divisions: 12,
activeColor: const Color(0xFF3B82F6),
onChanged: (v) => setState(() => _fontSize = v),
),
),
IconButton(
icon: const Icon(Icons.text_increase, size: 20),
color: _dark ? Colors.white : null,
onPressed: () => setState(() {
if (_fontSize < 24) _fontSize += 1;
}),
),
const SizedBox(width: 4),
Text(
'深色',
style: TextStyle(fontSize: 13, color: _dark ? Colors.white70 : null),
),
Switch(
value: _dark,
activeThumbColor: const Color(0xFF3B82F6),
onChanged: (v) => setState(() => _dark = v),
),
const SizedBox(width: 4),
],
),
);
}
}