import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../services/api_service.dart'; import '../providers/app_provider.dart'; import '../models/knowledge.dart'; /// 知识点树 Widget — 左侧导航 class KnowledgeTreeWidget extends ConsumerStatefulWidget { final void Function(String? chapterFilter, String? displayName) onSelectChapter; const KnowledgeTreeWidget({super.key, required this.onSelectChapter}); @override ConsumerState createState() => _KnowledgeTreeWidgetState(); } class _KnowledgeTreeWidgetState extends ConsumerState { List _tree = []; String? _selectedCode; bool _isLoading = true; @override void initState() { super.initState(); _load(); } Future _load() async { final api = ref.read(apiServiceProvider); try { final data = await api.knowledgeTree(); setState(() { _tree = (data['tree'] as List?) ?.map((j) => KnowledgeNode.fromJson(j)) .toList() ?? []; _isLoading = false; }); } catch (_) { setState(() => _isLoading = false); } } @override Widget build(BuildContext context) { return Column( children: [ // Search Padding( padding: const EdgeInsets.all(8), child: TextField( decoration: InputDecoration( hintText: '搜索知识点...', prefixIcon: const Icon(Icons.search, size: 20), contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), isDense: true, ), onChanged: (v) { // TODO: filter tree locally }, ), ), // Tree Expanded( child: _isLoading ? const Center(child: CircularProgressIndicator()) : ListView( children: [ ListTile( title: const Text('全部题目'), selected: _selectedCode == null, onTap: () { setState(() => _selectedCode = null); widget.onSelectChapter(null, null); }, ), ..._tree.map((node) => _buildTreeNode(node, 0)), ], ), ), ], ); } Widget _buildTreeNode(KnowledgeNode node, int depth) { // Build chapter filter string: for top-level nodes, use "XX-name" format final chapterFilter = depth == 0 ? '${node.code}-${node.name}' : node.name; if (node.children.isEmpty) { return ListTile( contentPadding: EdgeInsets.only(left: 16.0 + depth * 16.0), dense: true, title: Text(node.name, style: const TextStyle(fontSize: 13)), selected: _selectedCode == node.code, onTap: () { setState(() => _selectedCode = node.code); widget.onSelectChapter(chapterFilter, node.name); }, ); } return ExpansionTile( tilePadding: EdgeInsets.only(left: depth * 16.0), title: Text( node.name, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500), ), initiallyExpanded: depth < 2, onExpansionChanged: (expanded) { if (expanded) { setState(() => _selectedCode = node.code); widget.onSelectChapter(chapterFilter, node.name); } }, children: node.children.map((c) => _buildTreeNode(c, depth + 1)).toList(), ); } }