diff --git a/README.md b/README.md
index 8bbac73..e810a36 100644
--- a/README.md
+++ b/README.md
@@ -76,6 +76,12 @@ flutter run
flutter build apk --release
# 或构建 App Bundle(推荐用于 Google Play)
flutter build appbundle --release
+
+# 使用国内镜像构建运行
+$env:PUB_HOSTED_URL="https://pub.flutter-io.cn"
+$env:FLUTTER_STORAGE_BASE_URL="https://storage.flutter-io.cn"
+flutter pub get
+flutter run
```
### 3. 连接设备或启动模拟器
diff --git a/android/build/reports/problems/problems-report.html b/android/build/reports/problems/problems-report.html
index ff901e1..2d58dd2 100644
--- a/android/build/reports/problems/problems-report.html
+++ b/android/build/reports/problems/problems-report.html
@@ -650,7 +650,7 @@ code + .copy-button {
diff --git a/lib/pages/book/book_tab_page.dart b/lib/pages/book/book_tab_page.dart
index 898bf42..cd1a8fd 100644
--- a/lib/pages/book/book_tab_page.dart
+++ b/lib/pages/book/book_tab_page.dart
@@ -46,7 +46,7 @@ class BookTabPage extends StatelessWidget {
color: const Color(0xFF1A1A1A),
backgroundColor: Colors.white,
child: GridView.builder(
- padding: const EdgeInsets.all(16),
+ padding: const EdgeInsets.fromLTRB(16, 16, 16, 100),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 0.55,
diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart
index f0be66a..f56fd0f 100644
--- a/lib/pages/home_page.dart
+++ b/lib/pages/home_page.dart
@@ -23,11 +23,21 @@ class _HomePageState extends State {
? CustomDrawer()
: null,
- // 主体内容 - 根据底部导航切换
- body: _buildBody(),
-
- // 底部导航栏
- bottomNavigationBar: const CustomBottomNavBar(),
+ // 主体内容 - 使用 Stack 让 dock 栏悬浮在内容上方
+ body: Stack(
+ children: [
+ // 底层:主体内容
+ _buildBody(),
+
+ // 顶层:悬浮 dock 栏
+ const Positioned(
+ left: 0,
+ right: 0,
+ bottom: 0,
+ child: CustomBottomNavBar(),
+ ),
+ ],
+ ),
);
}
diff --git a/lib/pages/movies/douban_webview_page.dart b/lib/pages/movies/douban_webview_page.dart
new file mode 100644
index 0000000..44b8fe5
--- /dev/null
+++ b/lib/pages/movies/douban_webview_page.dart
@@ -0,0 +1,387 @@
+import 'dart:convert';
+import 'package:flutter/material.dart';
+import 'package:webview_flutter/webview_flutter.dart';
+
+/// 豆瓣影视WebView页面 - 用于抓取影视信息
+class DoubanWebViewPage extends StatefulWidget {
+ final String url;
+
+ const DoubanWebViewPage({super.key, required this.url});
+
+ @override
+ State createState() => _DoubanWebViewPageState();
+}
+
+class _DoubanWebViewPageState extends State {
+ late WebViewController _controller;
+ bool _isLoading = true;
+ bool _canExtract = false;
+ bool _isExtracting = false; // 防止重复提取
+
+ @override
+ void initState() {
+ super.initState();
+ _initWebView();
+ }
+
+ @override
+ void dispose() {
+ // 清理 WebView 资源
+ _controller.loadRequest(Uri.parse('about:blank'));
+ super.dispose();
+ }
+
+ void _initWebView() {
+ _controller = WebViewController()
+ ..setJavaScriptMode(JavaScriptMode.unrestricted)
+ ..setNavigationDelegate(
+ NavigationDelegate(
+ onPageStarted: (String url) {
+ if (mounted) {
+ setState(() {
+ _isLoading = true;
+ });
+ }
+ },
+ onPageFinished: (String url) {
+ if (mounted) {
+ setState(() {
+ _isLoading = false;
+ _canExtract = url.contains('douban.com/subject');
+ });
+ }
+ },
+ ),
+ )
+ ..loadRequest(Uri.parse(widget.url));
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ backgroundColor: Colors.white,
+ appBar: AppBar(
+ title: const Text('豆瓣影视'),
+ leading: _buildBackButton(),
+ actions: [
+ // 提取按钮 - 始终显示
+ _buildActionButton(
+ icon: Icons.auto_fix_high_outlined,
+ onPressed: _showExtractedInfo,
+ tooltip: '提取信息',
+ ),
+ // 刷新按钮
+ _buildActionButton(
+ icon: Icons.refresh,
+ onPressed: () => _controller.reload(),
+ tooltip: '刷新',
+ ),
+ const SizedBox(width: 8),
+ ],
+ ),
+ body: Stack(
+ children: [
+ WebViewWidget(controller: _controller),
+ // 加载指示器
+ if (_isLoading)
+ const Center(
+ child: CircularProgressIndicator(),
+ ),
+ ],
+ ),
+
+ );
+ }
+
+ /// 构建返回按钮
+ Widget _buildBackButton() {
+ return Container(
+ margin: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
+ decoration: BoxDecoration(
+ color: Colors.black.withOpacity(0.3),
+ borderRadius: BorderRadius.circular(8),
+ ),
+ child: Material(
+ color: Colors.transparent,
+ child: InkWell(
+ onTap: () {
+ // 停止加载并返回
+ _controller.loadRequest(Uri.parse('about:blank'));
+ Navigator.pop(context);
+ },
+ borderRadius: BorderRadius.circular(8),
+ child: Container(
+ padding: const EdgeInsets.all(8),
+ child: const Icon(Icons.arrow_back, color: Colors.white, size: 22),
+ ),
+ ),
+ ),
+ );
+ }
+
+ /// 构建右上角操作按钮
+ Widget _buildActionButton({
+ required IconData icon,
+ required VoidCallback onPressed,
+ required String tooltip,
+ }) {
+ return Container(
+ margin: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
+ decoration: BoxDecoration(
+ color: Colors.white.withOpacity(0.9),
+ borderRadius: BorderRadius.circular(8),
+ ),
+ child: Material(
+ color: Colors.transparent,
+ child: InkWell(
+ onTap: onPressed,
+ borderRadius: BorderRadius.circular(8),
+ child: Container(
+ padding: const EdgeInsets.all(8),
+ child: Icon(icon, color: const Color(0xFF1A1A1A), size: 22),
+ ),
+ ),
+ ),
+ );
+ }
+
+ /// 显示提取的信息对话框
+ Future _showExtractedInfo() async {
+ // 先提取信息
+ final movieInfo = await _extractMovieInfo();
+ if (movieInfo == null) return;
+
+ // 显示提取的信息
+ if (mounted) {
+ showDialog(
+ context: context,
+ builder: (context) => AlertDialog(
+ backgroundColor: Colors.white,
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
+ title: const Text(
+ '提取的影视信息',
+ style: TextStyle(
+ fontSize: 18,
+ fontWeight: FontWeight.w600,
+ color: Color(0xFF1A1A1A),
+ ),
+ ),
+ content: SingleChildScrollView(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ _buildInfoRow('标题', movieInfo['title']?.toString() ?? '未提取到'),
+ _buildInfoRow('年份', movieInfo['year']?.toString() ?? '未提取到'),
+ _buildInfoRow('评分', movieInfo['rating']?.toString() ?? '未提取到'),
+ _buildInfoRow('导演', movieInfo['director']?.toString() ?? '未提取到'),
+ _buildInfoRow('类型', movieInfo['genres']?.toString() ?? '未提取到'),
+ _buildInfoRow('上映日期', movieInfo['releaseDate']?.toString() ?? '未提取到'),
+ if (movieInfo['summary'] != null)
+ _buildInfoRow('简介', movieInfo['summary'].toString().substring(0,
+ movieInfo['summary'].toString().length > 100 ? 100 : movieInfo['summary'].toString().length) + '...'),
+ ],
+ ),
+ ),
+ actions: [
+ TextButton(
+ onPressed: () => Navigator.pop(context),
+ child: const Text(
+ '取消',
+ style: TextStyle(color: Color(0xFF999999)),
+ ),
+ ),
+ TextButton(
+ onPressed: () {
+ Navigator.pop(context);
+ Navigator.pop(context, movieInfo);
+ },
+ child: const Text(
+ '使用此信息',
+ style: TextStyle(color: Color(0xFF1A1A1A), fontWeight: FontWeight.w600),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+ }
+
+ /// 构建信息行
+ Widget _buildInfoRow(String label, String value) {
+ return Padding(
+ padding: const EdgeInsets.only(bottom: 8),
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SizedBox(
+ width: 64,
+ child: Text(
+ label,
+ style: const TextStyle(
+ fontSize: 14,
+ color: Color(0xFF999999),
+ ),
+ ),
+ ),
+ Expanded(
+ child: Text(
+ value,
+ style: const TextStyle(
+ fontSize: 14,
+ color: Color(0xFF1A1A1A),
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ /// 提取影视信息
+ Future