flutter架 构项目基础
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user