📖 短故事模块
版本 PanGrowth

📊 短故事信息接口

提供查询短故事列表、分类、历史记录、收藏、点赞等信息的API接口。

🚀 快速开始

import 'package:pangrowth_content/pangrowth_content.dart';

// 获取短故事列表
final stories = await PangrowthContent.getStoryList(
  page: 1,
  count: 20,
  categoryId: 1,
);

// 搜索短故事
final results = await PangrowthContent.searchStory(
  '言情',
  page: 1,
  count: 20,
);

// 收藏短故事
await PangrowthContent.favoriteStory(
  'story_123',
  isFavorite: true,
);

📖 API列表

1. 获取分类列表

Future<List<Map<String, dynamic>>> getStoryCategoryList()

获取所有短故事分类信息。

返回值: 分类列表,每个分类包含分类信息的Map

示例:

final categories = await PangrowthContent.getStoryCategoryList();
for (var category in categories) {
  print('分类: ${category['name']}, ID: ${category['id']}');
}

2. 获取短故事列表

Future<List<StoryInfo>> getStoryList({
  int? page,        // 页码,从1开始
  int? count,       // 每页数量
  bool? order,      // 排序方式: true正序/false倒序
  int? categoryId,  // 分类ID筛选(可选)
})

获取短故事列表,支持分页和分类筛选。

参数说明:

  • page: 页码,从1开始,不传则使用默认值
  • count: 每页数量,不传则使用默认值(通常20)
  • order: 排序方式,true为正序,false为倒序
  • categoryId: 可选,指定分类ID进行筛选

返回值: List<StoryInfo> 短故事信息列表

示例:

// 获取第一页(默认参数)
final stories = await PangrowthContent.getStoryList();

// 获取指定分类的短故事
final categoryStories = await PangrowthContent.getStoryList(
  page: 1,
  count: 20,
  categoryId: 5,
  order: false,  // 倒序
);

3. 搜索短故事

Future<List<StoryInfo>> searchStory(
  String keyword, {
  bool? isFuzzy,  // 是否模糊搜索,默认true
  int? page,      // 页码,从1开始
  int? count,     // 每页数量
})

根据关键词搜索短故事。

参数说明:

  • keyword: 搜索关键词(必填)
  • isFuzzy: 是否模糊搜索,默认true
  • page: 页码,从1开始
  • count: 每页数量

返回值: List<StoryInfo> 匹配的短故事列表

示例:

// 模糊搜索
final results = await PangrowthContent.searchStory(
  '爱情',
  isFuzzy: true,
  page: 1,
  count: 20,
);

// 精确搜索
final exactResults = await PangrowthContent.searchStory(
  '霸道总裁',
  isFuzzy: false,
);

4. 获取阅读历史

Future<List<StoryInfo>> getStoryHistory({
  int? page,   // 页码,从1开始
  int? count,  // 每页数量
})

获取用户的短故事阅读历史记录。

参数说明:

  • page: 页码,从1开始
  • count: 每页数量

返回值: List<StoryInfo> 历史记录列表,按阅读时间倒序

示例:

final history = await PangrowthContent.getStoryHistory(
  page: 1,
  count: 20,
);

for (var story in history) {
  print('上次阅读: ${story.title}, 进度: ${story.progress}');
}

5. 收藏短故事

Future<bool> favoriteStory(
  String storyId, {
  bool isFavorite = true,  // 是否收藏,默认true
})

收藏或取消收藏短故事。

参数说明:

  • storyId: 短故事ID(必填,String类型)
  • isFavorite: true收藏,false取消收藏

返回值: bool 操作是否成功

示例:

// 收藏短故事
final success = await PangrowthContent.favoriteStory('story_123');

// 取消收藏
final removed = await PangrowthContent.favoriteStory(
  'story_123',
  isFavorite: false,
);

6. 获取收藏列表

Future<List<StoryInfo>> getFavoriteStories({
  int? page,   // 页码,从1开始
  int? count,  // 每页数量
})

获取用户收藏的短故事列表。

参数说明:

  • page: 页码,从1开始
  • count: 每页数量

返回值: List<StoryInfo> 收藏的短故事列表

示例:

final favorites = await PangrowthContent.getFavoriteStories(
  page: 1,
  count: 20,
);

for (var story in favorites) {
  print('收藏时间: ${story.favoriteTime}');
}

7. 标记为已读

Future<bool> markAsRead(String storyId)

标记短故事为已读状态。

参数说明:

  • storyId: 短故事ID(必填,String类型)

返回值: bool 操作是否成功

示例:

await PangrowthContent.markAsRead('story_123');

8. 点赞短故事

Future<bool> likeStory(
  String storyId, {
  bool isLiked = true,  // 是否点赞,默认true
})

点赞或取消点赞短故事。

参数说明:

  • storyId: 短故事ID(必填,String类型)
  • isLiked: true点赞,false取消点赞

返回值: bool 操作是否成功

示例:

// 点赞
await PangrowthContent.likeStory('story_123');

// 取消点赞
await PangrowthContent.likeStory('story_123', isLiked: false);

9. 更新阅读进度

Future<bool> updateReadProgress(
  String storyId,
  int readDuration,  // 阅读时长,单位毫秒
)

更新短故事的阅读进度和阅读时长。

参数说明:

  • storyId: 短故事ID(必填,String类型)
  • readDuration: 本次阅读时长,单位毫秒

返回值: bool 操作是否成功

示例:

// 记录阅读了5分钟
await PangrowthContent.updateReadProgress('story_123', 5 * 60 * 1000);

📊 StoryInfo 数据结构

短故事信息模型,包含短故事的完整信息。

class StoryInfo {
  /// 短故事ID(String类型)
  final String storyId;

  /// 标题
  final String title;

  /// 简介/描述
  final String description;

  /// 作者
  final String author;

  /// 封面图URL
  final String coverUrl;

  /// 封面图类型: 0封面图、1默认图、2无图
  final int imageType;

  /// 分类ID
  final int categoryId;

  /// 分类名称
  final String categoryName;

  /// 总章节数
  final int totalChapters;

  /// 创建时间(毫秒级时间戳)
  final int createTime;

  /// 当前阅读章节(从0开始)
  final int currentIndex;

  /// 当前章节阅读进度 0.0~1.0
  final double progress;

  /// 阅读统计次数
  final int statsCount;

  /// 是否已收藏
  final bool isFavorited;

  /// 收藏时间(秒级时间戳,0表示未收藏)
  final int favoriteTime;

  /// 第一章内容预览
  final String contentPreview;

  /// 额外透传字段
  final Map<String, dynamic> extras;
}

字段说明

字段类型说明
storyIdString短故事唯一标识(字符串形式)
titleString短故事标题
descriptionString简介/描述
authorString作者名称
coverUrlString封面图URL
imageTypeint封面类型: 0封面图/1默认图/2无图
categoryIdint所属分类ID
categoryNameString所属分类名称
totalChaptersint总章节数
createTimeint创建时间(毫秒时间戳)
currentIndexint当前阅读章节索引(从0开始)
progressdouble当前章节阅读进度(0.0-1.0)
statsCountint阅读统计次数
isFavoritedbool是否已收藏
favoriteTimeint收藏时间(秒级时间戳,0表示未收藏)
contentPreviewString第一章内容预览
extrasMap<String, dynamic>额外透传字段

使用示例

final story = stories.first;

print('故事ID: ${story.storyId}');
print('标题: ${story.title}');
print('作者: ${story.author}');
print('分类: ${story.categoryName}');
print('总章节: ${story.totalChapters}章');
print('当前进度: 第${story.currentIndex + 1}章, ${(story.progress * 100).toStringAsFixed(1)}%');
print('是否收藏: ${story.isFavorited ? "是" : "否"}');
print('阅读次数: ${story.statsCount}');

// 显示封面图
if (story.imageType == 0) {
  Image.network(story.coverUrl);
}

💡 使用场景示例

场景1: 分类浏览

class StoryBrowsePage extends StatefulWidget {
  @override
  _StoryBrowsePageState createState() => _StoryBrowsePageState();
}

class _StoryBrowsePageState extends State<StoryBrowsePage> {
  List<Map<String, dynamic>> _categories = [];
  List<StoryInfo> _stories = [];
  int? _selectedCategoryId;

  @override
  void initState() {
    super.initState();
    _loadCategories();
  }

  Future<void> _loadCategories() async {
    final categories = await PangrowthContent.getStoryCategoryList();
    setState(() => _categories = categories);

    // 默认加载第一个分类
    if (categories.isNotEmpty) {
      _loadStoriesByCategory(categories.first['id']);
    }
  }

  Future<void> _loadStoriesByCategory(int categoryId) async {
    final stories = await PangrowthContent.getStoryList(
      page: 1,
      count: 20,
      categoryId: categoryId,
    );
    setState(() {
      _selectedCategoryId = categoryId;
      _stories = stories;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('短故事分类')),
      body: Column(
        children: [
          // 分类标签栏
          SingleChildScrollView(
            scrollDirection: Axis.horizontal,
            child: Row(
              children: _categories.map((category) {
                final isSelected = category['id'] == _selectedCategoryId;
                return Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 8),
                  child: ChoiceChip(
                    label: Text(category['name']),
                    selected: isSelected,
                    onSelected: (_) => _loadStoriesByCategory(category['id']),
                  ),
                );
              }).toList(),
            ),
          ),

          // 故事列表
          Expanded(
            child: ListView.builder(
              itemCount: _stories.length,
              itemBuilder: (context, index) {
                final story = _stories[index];
                return ListTile(
                  leading: story.imageType == 0
                      ? Image.network(story.coverUrl, width: 60)
                      : const Icon(Icons.book),
                  title: Text(story.title),
                  subtitle: Text('${story.author} · ${story.totalChapters}章'),
                  trailing: IconButton(
                    icon: Icon(
                      story.isFavorited ? Icons.favorite : Icons.favorite_border,
                      color: story.isFavorited ? Colors.red : null,
                    ),
                    onPressed: () => _toggleFavorite(story),
                  ),
                  onTap: () => _openReader(story),
                );
              },
            ),
          ),
        ],
      ),
    );
  }

  Future<void> _toggleFavorite(StoryInfo story) async {
    final success = await PangrowthContent.favoriteStory(
      story.storyId,
      isFavorite: !story.isFavorited,
    );
    if (success) {
      // 刷新列表
      _loadStoriesByCategory(_selectedCategoryId!);
    }
  }

  void _openReader(StoryInfo story) {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (_) => StoryReaderPage(
          storyId: int.parse(story.storyId),
          storyName: story.title,
        ),
      ),
    );
  }
}

场景2: 搜索功能

class StorySearchPage extends StatefulWidget {
  @override
  _StorySearchPageState createState() => _StorySearchPageState();
}

class _StorySearchPageState extends State<StorySearchPage> {
  final TextEditingController _searchController = TextEditingController();
  List<StoryInfo> _searchResults = [];
  bool _isSearching = false;

  Future<void> _performSearch(String keyword) async {
    if (keyword.isEmpty) return;

    setState(() => _isSearching = true);

    try {
      final results = await PangrowthContent.searchStory(
        keyword,
        isFuzzy: true,
        page: 1,
        count: 50,
      );
      setState(() => _searchResults = results);
    } finally {
      setState(() => _isSearching = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: TextField(
          controller: _searchController,
          decoration: const InputDecoration(
            hintText: '搜索短故事...',
            border: InputBorder.none,
          ),
          onSubmitted: _performSearch,
        ),
        actions: [
          IconButton(
            icon: const Icon(Icons.search),
            onPressed: () => _performSearch(_searchController.text),
          ),
        ],
      ),
      body: _isSearching
          ? const Center(child: CircularProgressIndicator())
          : _searchResults.isEmpty
              ? const Center(child: Text('暂无搜索结果'))
              : ListView.builder(
                  itemCount: _searchResults.length,
                  itemBuilder: (context, index) {
                    final story = _searchResults[index];
                    return ListTile(
                      leading: story.imageType == 0
                          ? Image.network(story.coverUrl, width: 60)
                          : null,
                      title: Text(story.title),
                      subtitle: Text(story.description),
                      onTap: () {
                        // 跳转到阅读器
                      },
                    );
                  },
                ),
    );
  }
}

场景3: 阅读历史和进度管理

class StoryHistoryPage extends StatefulWidget {
  @override
  _StoryHistoryPageState createState() => _StoryHistoryPageState();
}

class _StoryHistoryPageState extends State<StoryHistoryPage> {
  List<StoryInfo> _history = [];

  @override
  void initState() {
    super.initState();
    _loadHistory();
  }

  Future<void> _loadHistory() async {
    final history = await PangrowthContent.getStoryHistory(
      page: 1,
      count: 50,
    );
    setState(() => _history = history);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('阅读历史')),
      body: ListView.builder(
        itemCount: _history.length,
        itemBuilder: (context, index) {
          final story = _history[index];
          final progressPercent = (story.progress * 100).toStringAsFixed(0);

          return Card(
            margin: const EdgeInsets.all(8),
            child: ListTile(
              leading: story.imageType == 0
                  ? Image.network(story.coverUrl, width: 60)
                  : const Icon(Icons.book),
              title: Text(story.title),
              subtitle: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text('已读至: 第${story.currentIndex + 1}章'),
                  LinearProgressIndicator(
                    value: story.progress,
                    backgroundColor: Colors.grey[300],
                  ),
                  Text('进度: $progressPercent%'),
                ],
              ),
              trailing: IconButton(
                icon: const Icon(Icons.play_arrow),
                onPressed: () => _continueReading(story),
              ),
            ),
          );
        },
      ),
    );
  }

  void _continueReading(StoryInfo story) {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (_) => StoryReaderPage(
          storyId: int.parse(story.storyId),
          storyName: story.title,
          startChapter: story.currentIndex,
        ),
      ),
    );
  }
}

⚠️ 注意事项

1. ID类型

  • 短故事ID (storyId) 是 String 类型,不是 int
  • 分类ID (categoryId) 是 int 类型
  • 使用时注意类型转换

2. 时间戳格式

  • createTime: 毫秒级时间戳
  • favoriteTime: 秒级时间戳
  • 注意转换时的单位差异

3. 进度值范围

  • progress: 0.0 ~ 1.0 之间的浮点数
  • 显示百分比时需要乘以100

4. 封面图类型

  • imageType = 0: 有封面图,使用 coverUrl
  • imageType = 1: 使用默认图
  • imageType = 2: 无图

🔗 相关文档


需要进一步协助?

与 LightCore 技术顾问沟通,获取商业化策略与集成支持。