feat:增加菜单栏模块

This commit is contained in:
2025-11-14 17:29:54 +08:00
parent 3055f99aab
commit 94849be69c
17 changed files with 1376 additions and 252 deletions

View File

@@ -1,130 +1,200 @@
import 'package:blog_app/test.dart';
import 'package:flutter/material.dart';
import 'package:markdown_widget/markdown_widget.dart';
void main() {
runApp(const MyApp());
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Markdown导航'),
title: Text('博客卡片Demo'),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
body: const MarkdownWithToc(),
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Colors.blue.shade50, Colors.purple.shade50],
),
),
child: SingleChildScrollView(
padding: EdgeInsets.all(16),
child: Column(
children: [
BlogCard(
title: 'Flutter开发实战',
description: '本文详细介绍了如何使用Flutter开发高性能的跨平台应用包含最佳实践和性能优化技巧。通过实际案例演示了状态管理、路由导航、网络请求等核心功能的实现方式帮助开发者快速掌握Flutter开发精髓。',
labels: [
BlogLabel('精品', Icons.star, Colors.amber),
BlogLabel('置顶', Icons.push_pin, Colors.red),
BlogLabel('技术', Icons.category, Colors.blue),
BlogLabel('2024-01-15', Icons.calendar_today, Colors.green),
BlogLabel('2024-01-20', Icons.update, Colors.orange),
BlogLabel('2.3k', Icons.remove_red_eye, Colors.purple),
BlogLabel('3.2k字', Icons.text_fields, Colors.indigo),
BlogLabel('8分钟', Icons.access_time, Colors.brown),
BlogLabel('24', Icons.comment, Colors.teal),
],
),
SizedBox(height: 20),
BlogCard(
title: 'Dart语言特性深入解析',
description: '深入探讨Dart语言的现代特性包括空安全、异步编程、扩展方法等高级功能。结合实例讲解如何利用Dart的特性编写更安全、更高效的代码提升开发效率和代码质量。',
labels: [
BlogLabel('推荐', Icons.thumb_up, Colors.pink),
BlogLabel('编程', Icons.code, Colors.blue),
BlogLabel('2024-01-10', Icons.calendar_today, Colors.green),
BlogLabel('2024-01-18', Icons.update, Colors.orange),
BlogLabel('1.5k', Icons.remove_red_eye, Colors.purple),
BlogLabel('2.1k字', Icons.text_fields, Colors.indigo),
BlogLabel('6分钟', Icons.access_time, Colors.brown),
BlogLabel('18', Icons.comment, Colors.teal),
],
),
],
),
),
)
),
theme: ThemeData(
fontFamily: 'CustomFont'
),
);
}
}
class MarkdownWithToc extends StatefulWidget {
const MarkdownWithToc({super.key});
class BlogLabel {
final String text;
final IconData icon;
final Color color;
@override
State<MarkdownWithToc> createState() => _MarkdownWithTocState();
BlogLabel(this.text, this.icon, this.color);
}
class _MarkdownWithTocState extends State<MarkdownWithToc> {
final tocController = TocController();
bool _showToc = false;
class BlogCard extends StatelessWidget {
final String title;
final String description;
final List<BlogLabel> labels;
// ... markdownData 同上
const BlogCard({
super.key,
required this.title,
required this.description,
required this.labels,
});
Widget _buildTocPanel() => AnimatedOpacity(
opacity: _showToc ? 1.0 : 0.0,
duration: const Duration(milliseconds: 300),
child: Visibility(
visible: _showToc,
child: Align(
alignment: Alignment.bottomRight,
child: Container(
width: 250,
height: 400,
margin: const EdgeInsets.only(bottom: 60, right: 60), // 调整位置在按钮左侧
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
border: Border.all(color: Colors.grey[300]!),
@override
Widget build(BuildContext context) {
return Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Container(
width: double.infinity, // 确保宽度对齐
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Colors.white, Colors.grey.shade50],
),
),
child: Padding(
padding: EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// 标题
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
),
// 标题 - 居中
Text(
title,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.blue.shade800,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'目录',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 16),
// 单行标签 - 居中,更小的尺寸
Container(
width: double.infinity,
child: Wrap(
spacing: 6,
runSpacing: 6,
alignment: WrapAlignment.center,
children: labels.map((label) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: label.color.withAlpha(30),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: label.color.withAlpha(20),
width: 1,
),
),
),
IconButton(
icon: const Icon(Icons.close, size: 20),
onPressed: () => setState(() => _showToc = false),
),
],
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(label.icon, size: 12, color: label.color),
SizedBox(width: 4),
Text(
label.text,
style: TextStyle(
fontSize: 10,
color: label.color,
fontWeight: FontWeight.w500,
),
),
],
),
);
}).toList(),
),
),
Expanded(
child: TocWidget(
controller: tocController,
SizedBox(height: 16),
// 分隔线
Container(
height: 1,
width: 80,
color: Colors.blue.shade200,
),
SizedBox(height: 16),
// 描述 - 放在下面大约100字
Container(
width: double.infinity,
child: Text(
description,
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade700,
height: 1.6,
),
textAlign: TextAlign.justify,
maxLines: 4,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
),
),
);
Widget buildMarkdown() => MarkdownWidget(
data: markdownData,
tocController: tocController,
);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('文档'),
),
body: Stack(
children: [
// 主内容
buildMarkdown(),
// 悬浮TOC面板
_buildTocPanel(),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () => setState(() => _showToc = !_showToc),
child: Icon(_showToc ? Icons.close : Icons.list),
mini: true,
backgroundColor: _showToc ? Colors.grey : Theme.of(context).primaryColor,
),
);
}
}