refactor: 重构项目目录结构与路径引用

1.  调整工具类、平台相关代码的目录组织,将原有根目录下的工具类迁移到`data/`和`platform/`子目录
2.  统一修复全项目的文件导入路径,匹配新的目录结构
3.  新增Web端平台适配的Stub实现,包括Isolate、path_provider、platform_io等
4.  删除旧的单文件平台适配实现,替换为分平台的目录结构实现
5.  移除旧的iOS Widget入口文件,新增Widget Extension的权限配置
6.  调整部分组件的目录位置,统一widget的分类组织
7.  修复部分硬编码文本和废弃的正则表达式逻辑
This commit is contained in:
Developer
2026-05-23 05:16:31 +08:00
parent 85d856f0ed
commit a9499d7219
357 changed files with 6505 additions and 2532 deletions

View File

@@ -0,0 +1,793 @@
# 闲言APP Bug修复与功能增强 实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 修复5个关键Bug + 实现9项功能增强 + 2个目录重构
**Architecture:** 按优先级分4批执行P0紧急Bug修复 → P1功能增强 → P2交互优化 → P3目录重构
**Tech Stack:** Flutter/Dart, Riverpod, Cupertino, confetti, flutter_animate, fl_chart, flutter_quill
---
## 批次一P0 紧急Bug修复Task 1-5
### Task 1: 修复国学经典频道ID映射错误
**Files:**
- Modify: `lib/features/classics/services/classics_service.dart:16-28`
- Modify: `lib/features/tool_center/health/services/health_service.dart:16-28`
**问题:** 客户端硬编码的频道ID与后端 `$feedMap` key 不匹配导致API报"不支持的频道"错误。
**后端正确的key映射表:**
| 分类名 | 客户端当前ID | 后端正确ID |
|--------|------------|-----------|
| 黄帝内经 | `huangdi` | `hdnj` |
| 史记 | `shiji` | `sj` |
| 孙子兵法 | `sunzi` | `sbbf` |
| 墨子 | `mozi` | `mz` |
| 庄子 | `zhuangzi` | `zz` |
| 三国志 | `sanguozhi` | `sgz` |
| 金匮要略 | `jingui` | `jgj` |
| 战国策 | `zhanguoce` | `warring` |
| 万历野获编 | `wanli` | 无对应(需后端新增或移除) |
| 药茶养生 | `tea` | `tisana` |
| 疾病自查 | `disease` | `illness` |
- [ ] **Step 1: 修复 classics_service.dart 中的ID映射**
`ClassicsCategory``id` 字段改为与后端一致:
```dart
static const List<ClassicsCategory> categories = [
ClassicsCategory(id: 'lunyu', name: '论语', emoji: '📖', desc: '孔子言行录'),
ClassicsCategory(id: 'hdnj', name: '黄帝内经', emoji: '🏥', desc: '中医经典'),
ClassicsCategory(id: 'sj', name: '史记', emoji: '📜', desc: '司马迁著'),
ClassicsCategory(id: 'sbbf', name: '孙子兵法', emoji: '⚔️', desc: '兵家圣典'),
ClassicsCategory(id: 'mz', name: '墨子', emoji: '🖤', desc: '墨家经典'),
ClassicsCategory(id: 'zz', name: '庄子', emoji: '🦋', desc: '道家经典'),
ClassicsCategory(id: 'zuozhuan', name: '左传', emoji: '📚', desc: '春秋左氏传'),
ClassicsCategory(id: 'sgz', name: '三国志', emoji: '🏯', desc: '陈寿著'),
ClassicsCategory(id: 'jgj', name: '金匮要略', emoji: '💊', desc: '张仲景著'),
ClassicsCategory(id: 'warring', name: '战国策', emoji: '🗡️', desc: '纵横家书'),
];
```
移除 `wanli`(万历野获编),因为后端无对应频道。
- [ ] **Step 2: 修复 health_service.dart 中的ID映射**
```dart
static const List<HealthCategory> categories = [
HealthCategory(id: 'drug', name: '药品查询', emoji: '💊', desc: '药品信息·用法用量'),
HealthCategory(id: 'herbal', name: '中草药', emoji: '🌿', desc: '中草药百科'),
HealthCategory(id: 'food', name: '食物相克', emoji: '🍎', desc: '食物搭配禁忌'),
HealthCategory(id: 'prescription', name: '偏方大全', emoji: '📋', desc: '民间偏方验方'),
HealthCategory(id: 'tisana', name: '药茶养生', emoji: '🍵', desc: '养生药茶配方'),
HealthCategory(id: 'illness', name: '疾病自查', emoji: '🏥', desc: '症状·疾病查询'),
];
```
- [ ] **Step 3: 搜索所有引用旧ID的代码并更新**
搜索 `huangdi|shiji|sunzi|mozi|zhuangzi|sanguozhi|jingui|zhanguoce|wanli``tea|disease` 在整个 `lib/` 目录中的引用确保没有硬编码的旧ID。
- [ ] **Step 4: 运行 flutter analyze 验证**
Run: `flutter analyze lib/features/classics/ lib/features/tool_center/health/`
Expected: No issues found
---
### Task 2: 修复离线模式预加载频道使用中文名
**Files:**
- Modify: `lib/core/storage/cache_config.dart:28,71-74`
**问题:** `preloadChannels` 默认值 `{'推荐', '经典', '诗词'}` 是中文,但后端 Feed API 的 channel 参数需要英文 key。
- [ ] **Step 1: 修改 CacheConfig 默认值**
```dart
// 将中文频道名改为英文key
this.preloadChannels = const {'all', 'poetry', 'wisdom'},
```
同步修改 fromJson 中的默认值:
```dart
preloadChannels: (json['preloadChannels'] as List<dynamic>?)
?.map((e) => e.toString())
.toSet() ??
const {'all', 'poetry', 'wisdom'},
```
- [ ] **Step 2: 修改 offline_page.dart 中频道显示**
在离线模式页面的"预加载频道"行中将英文key映射为中文名显示
```dart
_SettingRow(
icon: CupertinoIcons.cube_box,
title: '预加载频道',
trailing: Text(config.preloadChannels.map(_channelDisplayName).join('')),
),
```
添加映射方法:
```dart
String _channelDisplayName(String key) {
const map = {
'all': '推荐',
'poetry': '诗词',
'wisdom': '哲理',
'story': '故事',
'hitokoto': '一言',
'lyric': '歌词',
'saying': '名言',
};
return map[key] ?? key;
}
```
- [ ] **Step 3: 运行 flutter analyze 验证**
---
### Task 3: 合并两套独立的预加载开关
**Files:**
- Modify: `lib/mine/settings/providers/sub/performance_settings_provider.dart`
- Modify: `lib/mine/settings/providers/general_settings_provider.dart`
- Modify: `lib/mine/settings/presentation/general/general_settings_sections.dart`
- Modify: `lib/features/home/presentation/providers/offline_page.dart`
- Modify: `lib/features/home/services/offline_manager.dart`
**问题:** 通用设置中的 `preloadEnabled`KV key: `general_preload`)和离线模式中的 `preloadOnWifi`CacheConfig互不关联。
**方案:** 统一使用 `CacheConfig.preloadOnWifi` 作为唯一预加载开关,移除 `PerformanceSettingsState.preloadEnabled`
- [ ] **Step 1: 从 PerformanceSettingsState 移除 preloadEnabled**
移除 `preloadEnabled` 字段、`fromStorage` 中的读取、`saveToStorage` 中的保存、`setPreloadEnabled` 方法。
- [ ] **Step 2: 从 GeneralSettingsState 移除 preloadEnabled 透传**
移除 `bool get preloadEnabled``setPreloadEnabled` 方法。
- [ ] **Step 3: 修改通用设置页面**
将"预加载"开关改为读取/写入 `CacheConfig.preloadOnWifi`(通过 `offlineProvider`),而非 `generalSettingsProvider`
- [ ] **Step 4: 修改 OfflineManager.preloadNow()**
`preloadNow()` 开头检查 `CacheConfig.preloadOnWifi`,如果关闭则返回 `PreloadResult(loaded: 0, skipped: 0)` 并在调用方提示"预加载已关闭"。
- [ ] **Step 5: 运行 flutter analyze 验证**
---
### Task 4: 增强阅读报告错误提示
**Files:**
- Modify: `lib/features/reading_report/presentation/reading_report_page.dart`
- Modify: `lib/features/reading_report/providers/reading_report_provider.dart`
**问题:** 当 dashboard API 返回空或格式变化时,仅显示"报告加载失败",用户无法理解原因。
- [ ] **Step 1: 修改 ReadingReportState 增加部分加载状态**
```dart
class ReadingReportState {
final ReadingReport? report;
final ReportPeriod currentPeriod;
final bool isLoading;
final String? error;
final Set<String> failedSources; // 新增:记录哪些数据源加载失败
// copyWith 也需更新
}
```
- [ ] **Step 2: 修改 ReadingReportService.generateReport 返回失败源信息**
`generateReport`catch 块记录失败的数据源名称:
```dart
final failedSources = <String>{};
try { dashboard = await UserCenterService.getDashboard(); } catch (e) { failedSources.add('仪表盘'); }
try { statsData = await UserCenterService.getStats(...); } catch (e) { failedSources.add('趋势数据'); }
try { heatmapData = await UserCenterService.getHeatmap(); } catch (e) { failedSources.add('热力图'); }
```
- [ ] **Step 3: 修改阅读报告页面错误/部分加载提示**
`failedSources` 非空但 report 不为空时,在页面顶部显示警告条:
```dart
if (state.failedSources.isNotEmpty && state.report != null)
Container(
padding: const EdgeInsets.all(AppSpacing.sm),
decoration: BoxDecoration(color: CupertinoColors.systemYellow.withValues(alpha: 0.1), ...),
child: Row(children: [
Icon(CupertinoIcons.exclamationmark_triangle, color: CupertinoColors.systemYellow, size: 16),
Text('部分数据加载失败:${state.failedSources.join('')}', ...),
]),
),
```
当全部失败时,显示更友好的错误信息:
```dart
Widget _buildError(String error, AppThemeExtension ext) {
return Center(child: Column(children: [
Icon(CupertinoIcons.cloud_slash, size: 48, color: ext.textHint),
Text('数据源暂时不可用', ...),
Text('请检查网络连接后重试', ...),
CupertinoButton(onPressed: retry, child: Text('重新加载')),
]));
}
```
- [ ] **Step 4: 运行 flutter analyze 验证**
---
### Task 5: 排行榜赛季自动创建(后端定时任务)
**Files:**
- Create: `Scripts/create_rank_season.py` — Python脚本通过API创建赛季
- Modify: `docs/toolsapi/application/api/controller/Rank.php` — 添加自动创建当前赛季逻辑
**问题:** `rank_season` 表中没有 `status=active` 的记录,排行榜为空。
- [ ] **Step 1: 修改 Rank.php leaderboard 方法,自动创建当前周赛**
`leaderboard()` 方法中,当找不到活跃赛季时,自动创建:
```php
if (!$currentSeason) {
// 自动创建当前周赛
$now = time();
$weekStart = strtotime('monday this week', $now);
$weekEnd = $weekStart + 7 * 86400;
$weekNum = date('W', $now);
$year = date('Y', $now);
Db::name('rank_season')->insert([
'name' => "{$year}年第{$weekNum}周赛",
'type' => 'weekly',
'start_time' => $weekStart,
'end_time' => $weekEnd,
'status' => 'active',
'rewards' => json_encode([
['rank_start' => 1, 'rank_end' => 3, 'exp' => 100, 'score' => 50],
['rank_start' => 4, 'rank_end' => 10, 'exp' => 50, 'score' => 20],
['rank_start' => 11, 'rank_end' => 50, 'exp' => 20, 'score' => 10],
]),
'createtime' => $now,
'updatetime' => $now,
]);
$currentSeason = Db::name('rank_season')
->where('status', 'active')
->where('start_time', '<=', $now)
->where('end_time', '>', $now)
->order('start_time desc')
->find();
}
```
同样在 `myRank()` 方法中添加相同的自动创建逻辑。
- [ ] **Step 2: 上传修改后的 Rank.php 到服务器**
使用 `Scripts/upload_server_code.py` 上传。
- [ ] **Step 3: 运行测试脚本验证**
Run: `python Scripts/test_rank_api.py`
Expected: 排行榜API返回 `season != null`
---
## 批次二P1 功能增强Task 6-8
### Task 6: 排行榜前3名动画效果
**Files:**
- Modify: `lib/shared/widgets/rank_item_card.dart`
- Modify: `lib/features/rank/presentation/rank_page.dart`
- [ ] **Step 1: 为 RankItemCard 添加 flutter_animate 动画**
前三名添加缩放+光晕入场动画:
```dart
Widget build(BuildContext context) {
final widget = Container(/* 现有卡片内容 */);
if (item.rank <= 3) {
return widget
.animate()
.fadeIn(duration: 400.ms, delay: (item.rank * 100).ms)
.scale(begin: const Offset(0.9, 0.9), end: const Offset(1, 1), duration: 400.ms)
.shimmer(duration: 1200.ms, color: _rankColor.withValues(alpha: 0.15));
}
return widget.animate().fadeIn(duration: 300.ms, delay: (item.rank * 50).ms);
}
```
- [ ] **Step 2: 为第1名添加持续光晕效果**
```dart
if (item.rank == 1) {
return Container(
decoration: BoxDecoration(
borderRadius: AppRadius.mdBorder,
boxShadow: [
BoxShadow(
color: CupertinoColors.systemYellow.withValues(alpha: 0.3),
blurRadius: 12,
spreadRadius: 2,
),
],
),
child: widget,
).animate(onPlay: (c) => c.repeat()).shimmer(duration: 2000.ms, color: CupertinoColors.systemYellow.withValues(alpha: 0.1));
}
```
- [ ] **Step 3: 运行 flutter analyze 验证**
---
### Task 7: 阅读报告趋势图交互
**Files:**
- Modify: `lib/features/reading_report/presentation/widgets/trend_chart.dart`
- [ ] **Step 1: 为 fl_chart LineChart 添加 touchCallback**
```dart
LineChartData(
lineTouchData: LineTouchData(
touchTooltipData: LineTouchTooltipData(
tooltipBgColor: ext.bgSecondary,
tooltipRoundedRadius: 8,
getTooltipItems: (spots) => spots.map((spot) {
final date = trendData.length > spot.x.toInt()
? trendData[spot.x.toInt()].date
: '';
return LineTooltipItem(
'$date\n${spot.y.toInt()}',
AppTypography.caption1.copyWith(color: ext.textPrimary),
);
}).toList(),
),
handleBuiltInTouches: true,
),
// ... 现有配置
);
```
- [ ] **Step 2: 运行 flutter analyze 验证**
---
### Task 8: 排行榜领奖/成就解锁庆祝动画
**Files:**
- Modify: `lib/features/rank/presentation/rank_page.dart`
- [ ] **Step 1: 为 RankPage 添加 ConfettiController**
```dart
class _RankPageState extends ConsumerState<RankPage> {
late ConfettiController _confettiController;
@override
void initState() {
super.initState();
_confettiController = ConfettiController(duration: const Duration(seconds: 2));
}
@override
void dispose() {
_confettiController.dispose();
super.dispose();
}
}
```
- [ ] **Step 2: 在 Stack 中添加 ConfettiWidget**
```dart
Stack(children: [
// 现有页面内容
Align(
alignment: Alignment.topCenter,
child: ConfettiWidget(
confettiController: _confettiController,
blastDirectionality: BlastDirectionality.explosive,
emissionFrequency: 0.05,
numberOfParticles: 20,
colors: [ext.accent, CupertinoColors.systemYellow, CupertinoColors.systemGreen],
),
),
]);
```
- [ ] **Step 3: 领取奖励成功时触发动画**
`claimReward` 成功回调中调用 `_confettiController.play()`
- [ ] **Step 4: 运行 flutter analyze 验证**
---
## 批次三P2 交互优化Task 9-12
### Task 9: 笔记编辑器Markdown工具栏
**Files:**
- Modify: `lib/features/note/presentation/note_edit_page.dart`
- [ ] **Step 1: 在编辑区域上方添加Markdown快捷工具栏**
在标题输入框和内容输入框之间,添加一行工具栏:
```dart
Widget _buildMarkdownToolbar(AppThemeExtension ext) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.sm, vertical: 4),
decoration: BoxDecoration(color: ext.bgSecondary, borderRadius: AppRadius.smBorder),
child: Wrap(
spacing: 2,
runSpacing: 2,
children: [
_toolbarBtn(ext, CupertinoIcons.bold, '粗体', () => _insertMarkdown('**', '**')),
_toolbarBtn(ext, CupertinoIcons.italic, '斜体', () => _insertMarkdown('*', '*')),
_toolbarBtn(ext, CupertinoIcons.list_bullet, '列表', () => _insertMarkdown('\n- ', '')),
_toolbarBtn(ext, CupertinoIcons.number, '编号', () => _insertMarkdown('\n1. ', '')),
_toolbarBtn(ext, CupertinoIcons.quote_closing, '引用', () => _insertMarkdown('\n> ', '')),
_toolbarBtn(ext, CupertinoIcons.link, '链接', () => _insertMarkdown('[', '](url)')),
_toolbarBtn(ext, CupertinoIcons.code, '代码', () => _insertMarkdown('`', '`')),
_toolbarBtn(ext, CupertinoIcons.text_justify, '分割线', () => _insertMarkdown('\n---\n', '')),
],
),
);
}
Widget _toolbarBtn(AppThemeExtension ext, IconData icon, String tooltip, VoidCallback onTap) {
return CupertinoButton(
padding: const EdgeInsets.all(6),
minSize: 32,
onPressed: onTap,
child: Icon(icon, size: 18, color: ext.textSecondary),
);
}
```
- [ ] **Step 2: 实现 _insertMarkdown 方法**
在光标位置前后插入 Markdown 标记:
```dart
void _insertMarkdown(String before, String after) {
final controller = _contentController;
final sel = controller.selection;
final text = controller.text;
final selected = sel.textInside(text);
final newText = text.replaceRange(sel.start, sel.end, '$before$selected$after');
controller.text = newText;
controller.selection = TextSelection.collapsed(
offset: sel.start + before.length + selected.length,
);
setState(() => _hasUnsavedChanges = true);
_debounceSave();
}
```
- [ ] **Step 3: 运行 flutter analyze 验证**
---
### Task 10: SafeCachedImage 增加预加载和渐进式加载
**Files:**
- Modify: `lib/shared/widgets/safe_cached_image.dart`
- [ ] **Step 1: 添加预加载静态方法**
```dart
static Future<void> preload(String url) async {
if (!_isValidUrl(url)) return;
try {
await CachedNetworkImage.evictFromCache(url);
} catch (_) {}
}
```
- [ ] **Step 2: 添加渐进式加载占位符**
`CachedNetworkImage``progressIndicatorBuilder` 中显示下载进度:
```dart
progressIndicatorBuilder: (context, url, progress) {
if (progress.downloaded != null && progress.totalSize != null) {
final percent = (progress.downloaded! / progress.totalSize! * 100).toInt();
return CupertinoActivityIndicator.partiallyRevealed(progress: percent / 100);
}
return const CupertinoActivityIndicator();
},
```
- [ ] **Step 3: 运行 flutter analyze 验证**
---
### Task 11: 离线模式操作队列持久化
**Files:**
- Modify: `lib/features/home/services/offline_manager.dart`
- Modify: `lib/features/home/providers/offline_provider.dart`
- [ ] **Step 1: 在 OfflineManager.init() 中从 Hive 加载待执行操作**
```dart
static Future<void> init() async {
// ... 现有逻辑
await _loadPendingActions();
}
static Future<void> _loadPendingActions() async {
final box = await Hive.openBox('offlineQueue');
final saved = box.get('pendingActions', defaultValue: []) as List;
for (final item in saved) {
if (item is Map) {
_pendingActions.add(OfflineAction.fromMap(Map<String, dynamic>.from(item)));
}
}
}
```
- [ ] **Step 2: 在操作入队时持久化到 Hive**
```dart
static Future<void> enqueueAction(OfflineAction action) async {
_pendingActions.add(action);
final box = await Hive.openBox('offlineQueue');
await box.put('pendingActions', _pendingActions.map((a) => a.toMap()).toList());
}
```
- [ ] **Step 3: 在操作完成后从 Hive 移除**
```dart
static Future<void> _removeAction(int index) async {
_pendingActions.removeAt(index);
final box = await Hive.openBox('offlineQueue');
await box.put('pendingActions', _pendingActions.map((a) => a.toMap()).toList());
}
```
- [ ] **Step 4: 定义 OfflineAction 模型**
```dart
class OfflineAction {
final String type; // 'like', 'favorite', 'comment', etc.
final Map<String, dynamic> data;
final int createdAt;
const OfflineAction({required this.type, required this.data, required this.createdAt});
Map<String, dynamic> toMap() => {'type': type, 'data': data, 'createdAt': createdAt};
factory OfflineAction.fromMap(Map<String, dynamic> m) =>
OfflineAction(type: m['type'], data: Map<String, dynamic>.from(m['data']), createdAt: m['createdAt']);
}
```
- [ ] **Step 5: 运行 flutter analyze 验证**
---
### Task 12: 深度链接支持 + 鸿蒙端路由同步
**Files:**
- Modify: `lib/core/router/app_routes.dart`
- Modify: `lib/core/router/content_routes.dart`
- Modify: `lib/core/router/ohos_nav_bridge.dart`
- Modify: `lib/core/router/settings_routes.dart`
- [ ] **Step 1: 在 app_routes.dart 中添加深度链接路由常量**
```dart
static const String deepArticle = '/article/:id';
static const String deepFortune = '/fortune';
static const String deepNote = '/notes';
```
- [ ] **Step 2: 在 GoRouter 中注册深度链接处理**
`content_routes.dart``app_router.dart` 中,添加 `redirect` 逻辑处理通用链接:
```dart
redirect: (context, state) {
// 处理通用链接: xianyan://article/123 → /article/123
final uri = state.uri;
if (uri.scheme == 'xianyan') {
return uri.path;
}
return null;
},
```
- [ ] **Step 3: 在 ohos_nav_bridge.dart 中同步新增路由**
确保所有新增路由都在 `_routes` 列表中有对应的 `OhosRouteEntry`
```dart
OhosRouteEntry(pattern: '/other-settings', builder: (_) => const OtherSettingsPage()),
OhosRouteEntry(pattern: '/article/:id', builder: (ctx) => ArticleDetailPage(articleId: ctx.params.getInt('id'))),
```
- [ ] **Step 4: 运行 flutter analyze 验证**
---
## 批次四P3 目录重构Task 13-14
### Task 13: 重构 lib/shared/widgets/ 目录
**当前问题:** 36个文件平铺在一个目录下难以维护。
**目标结构:**
```
lib/shared/widgets/
├── widgets.dart # 统一导出(保持不变)
├── adaptive/ # 自适应组件
│ ├── adaptive_back_button.dart
│ └── responsive_layout.dart
├── animation/ # 动画组件
│ ├── animated_widgets.dart
│ └── sprite_loading_indicator.dart
├── containers/ # 容器组件
│ ├── glass_container.dart
│ ├── glass_bottom_nav_bar.dart
│ └── shader_card_background.dart
├── feedback/ # 反馈组件
│ ├── app_toast.dart
│ ├── empty_state.dart
│ ├── skeleton.dart
│ ├── app_error_boundary.dart
│ └── offline_banner.dart
├── input/ # 输入组件
│ ├── bottom_sheet.dart
│ ├── keyboard_back_handler.dart
│ ├── keyboard_safe_sheet.dart
│ └── app_popup_menu.dart
├── media/ # 媒体组件
│ ├── safe_cached_image.dart
│ ├── tts_player_bar.dart
│ └── wallpaper_gallery/ # 保持子目录不变
├── navigation/ # 导航组件
│ ├── appbar_character_sprite.dart
│ ├── appbar_date_display.dart
│ ├── tab_icon_sprite.dart
│ └── app_page_transitions.dart
├── content/ # 内容展示组件
│ ├── app_markdown.dart
│ ├── app_slidable.dart
│ ├── app_sticky_header.dart
│ ├── category_icon.dart
│ ├── level_card.dart
│ ├── rank_item_card.dart
│ ├── task_card.dart
│ ├── character_tip_bubble.dart
│ ├── share_sheet.dart
│ └── app_icon.dart
└── layout/ # 布局组件
└── (reserved for future)
```
- [ ] **Step 1: 创建子目录并移动文件**
按上述结构移动文件。每个文件移动后,更新内部 import 路径。
- [ ] **Step 2: 更新 widgets.dart 统一导出文件**
```dart
export 'adaptive/adaptive_back_button.dart';
export 'adaptive/responsive_layout.dart';
export 'animation/animated_widgets.dart';
export 'animation/sprite_loading_indicator.dart';
export 'containers/glass_container.dart';
export 'containers/glass_bottom_nav_bar.dart';
export 'containers/shader_card_background.dart';
export 'feedback/app_toast.dart';
export 'feedback/empty_state.dart';
export 'feedback/skeleton.dart';
export 'feedback/app_error_boundary.dart';
export 'feedback/offline_banner.dart';
export 'input/bottom_sheet.dart';
export 'input/keyboard_back_handler.dart';
export 'input/keyboard_safe_sheet.dart';
export 'input/app_popup_menu.dart';
export 'media/safe_cached_image.dart';
export 'media/tts_player_bar.dart';
export 'navigation/appbar_character_sprite.dart';
export 'navigation/appbar_date_display.dart';
export 'navigation/tab_icon_sprite.dart';
export 'navigation/app_page_transitions.dart';
export 'content/app_markdown.dart';
export 'content/app_slidable.dart';
export 'content/app_sticky_header.dart';
export 'content/category_icon.dart';
export 'content/level_card.dart';
export 'content/rank_item_card.dart';
export 'content/task_card.dart';
export 'content/character_tip_bubble.dart';
export 'content/share_sheet.dart';
export 'content/app_icon.dart';
```
- [ ] **Step 3: 全局搜索并更新所有 import 路径**
搜索 `package:xianyan/shared/widgets/` 开头的 import确保通过 `widgets.dart` 统一导出的路径仍然有效(因为 widgets.dart 重新导出了所有组件,大部分 import 不需要改)。
对于直接引用子文件的 import`package:xianyan/shared/widgets/glass_container.dart`),更新为新路径。
- [ ] **Step 4: 运行 flutter analyze 验证**
---
### Task 14: 重构 lib/core/utils/ 目录
**当前问题:** 18个文件平铺在一个目录下。
**目标结构:**
```
lib/core/utils/
├── logger.dart # 日志(保持不变,被广泛引用)
├── extensions.dart # 扩展方法(保持不变)
├── pattern_utils.dart # 正则工具(保持不变)
├── platform/ # 平台相关
│ ├── platform_utils.dart
│ ├── platform_helper.dart
│ ├── platform_feature_guard.dart
│ ├── platform_io_native.dart
│ ├── platform_io_stub.dart
│ ├── device_detection.dart
│ └── path_provider_native.dart
│ └── path_provider_stub.dart
├── ui/ # UI工具
│ ├── page_transitions.dart
│ ├── interaction_animations.dart
│ ├── sheet_animation_notifier.dart
│ └── clipboard_bridge.dart
├── data/ # 数据工具
│ ├── level_utils.dart
│ └── receipt_helper.dart
└── isolate_stub.dart # isolate保持不变
```
- [ ] **Step 1: 创建子目录并移动文件**
- [ ] **Step 2: 全局搜索并更新所有 import 路径**
- [ ] **Step 3: 运行 flutter analyze 验证**
---
## 验收标准
| 批次 | 验收项 |
|------|--------|
| P0 | `flutter analyze lib/` 零 error国学经典/健康模块所有分类可正常加载预加载使用英文key只有一套预加载开关阅读报告有友好错误提示排行榜自动创建赛季 |
| P1 | 排行榜前3名有缩放+光晕动画;趋势图可点击查看数据详情;领奖有庆祝动画 |
| P2 | 笔记编辑器有Markdown工具栏图片有渐进式加载离线操作重启后不丢失深度链接可跳转鸿蒙端路由同步 |
| P3 | widgets目录按职责分子目录utils目录按职责分子目录所有import路径正确 |