| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120 |
- 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<KnowledgeTreeWidget> createState() => _KnowledgeTreeWidgetState();
- }
- class _KnowledgeTreeWidgetState extends ConsumerState<KnowledgeTreeWidget> {
- List<KnowledgeNode> _tree = [];
- String? _selectedCode;
- bool _isLoading = true;
- @override
- void initState() {
- super.initState();
- _load();
- }
- Future<void> _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(),
- );
- }
- }
|