flutter架 构项目基础

This commit is contained in:
崔宝新
2026-09-04 15:22:20 +08:00
parent 70967229d0
commit 0c5aee59c0
265 changed files with 35484 additions and 0 deletions
+123
View File
@@ -0,0 +1,123 @@
# 【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 这类包逐标签渲染。
---
## 二、怎么运行 / 演示内容
```dart
runApp(const MaterialApp(home: RichTextDemoPage()));
```
- 顶部控制栏:滑块拖字号(12~24)、切深色开关 → 正文实时变化;
- 页面里:化学方程式上下标、本地图片(`asset://` 协议)、表格、听力音频、视频、红色加粗的答案 B。
---
## 三、核心知识点(新手向)
### 1. 一行渲染 HTML
```dart
RichTextContent(
html: _html, // 后端返回的 HTML 字符串
// 字号 + 深色模式都通过 textStyle 注入(学生端统一入口)
textStyle: TextStyle(
fontSize: _fontSize,
height: 1.6,
color: _dark ? Colors.white70 : const Color(0xFF333333),
),
)
```
**`textStyle` 注入是核心设计**:页面不重建 HTML、不改渲染器,只改一个 TextStyle 就全局换字号/颜色——学生端"无障碍调节"的标准做法。
### 2. 自定义样式:customStylesBuilder
```dart
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:// 协议
```html
<img src="asset://assets/img/luola_logo.png" width="160" height="160"/>
```
`asset://` + 资源路径 = 加载本地资源图片(最稳,不受网络影响)。实际后端返回完整 URL 或相对路径(相对路径需业务层拼 baseUrl)。
### 4. audio 标签自动替换
```html
<audio src="asset://assets/audio/sample-30s.mp3" title="听力材料(30 秒)" controls></audio>
```
组件内部检测 `<audio>` → 替换成 `AudioPlayerCard`(见 [audio_player_demo.md](audio_player_demo.md))——听力题直接能播。
---
## 四、关键代码剖析
```dart
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 档):
```dart
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](theme_demo.md))。
---
## 六、扩展思考
- `<audio>` 替换成的播放器组件见 [audio_player_demo.md](audio_player_demo.md);
- 富文本在真实考试页的运用见 [exam_demo.md](exam_demo.md)(题干 HTML 渲染);
- 学生端"字号调节 + 深色模式"这类**偏好设置**,进阶做法是存进全局状态(Riverpod),见 [exam_architecture.md](../exam_architecture.md) 的 Riverpod 章节;
- 真实项目:`_html` 换成接口返回的题目 HTML,`asset://` 换成 CDN 完整 URL 即可。