Files
nj/app/lib/features/stickers/views/sticker_library_page.dart
iven b320641d9c
Some checks failed
Main Merge / backend (push) Has been cancelled
Main Merge / frontend (push) Has been cancelled
fix(app): 全链路验证修复 — 编译错误/CORS/迁移/启动脚本
前端修复:
- calendar_page: 移除不存在的 JournalEntry.content getter
- responsive_scaffold: 移除不存在的 notchThickness 参数
- splash_page: SingleTickerProvider → TickerProvider (多 AnimationController)
- profile_page: UserRoleType.name → .code (修复运行时崩溃)
- 导入缺失的 user.dart

后端修复:
- class_service: generate_class_code 取 UUID 后6位(随机部分)避免碰撞
- diary_role_seed: 移除不存在的 id 列,使用复合主键 ON CONFLICT

基础设施:
- config/default.toml: CORS 改为通配符(开发模式)
- scripts/dev.sh: 统一启动脚本(自动清理端口)
- docs/opendesign/: Open Design 设计规格 HTML 原型稿

验证结果: flutter analyze 0 error, cargo test 77/77 通过, 17个页面全部渲染正常
2026-06-02 01:03:58 +08:00

215 lines
6.9 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 贴纸库页面 — 贴纸包浏览 + 分类 Tab
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:nuanji_app/core/theme/app_colors.dart';
import 'package:nuanji_app/core/theme/app_radius.dart';
import 'package:nuanji_app/data/remote/api_client.dart';
import '../bloc/sticker_bloc.dart';
/// 贴纸库页面 — 分类浏览贴纸包
class StickerLibraryPage extends StatefulWidget {
const StickerLibraryPage({super.key});
@override
State<StickerLibraryPage> createState() => _StickerLibraryPageState();
}
class _StickerLibraryPageState extends State<StickerLibraryPage> {
late final StickerBloc _bloc;
@override
void initState() {
super.initState();
_bloc = StickerBloc(api: context.read<ApiClient>());
_bloc.load();
}
@override
void dispose() {
_bloc.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
appBar: AppBar(title: const Text('贴纸库')),
body: ListenableBuilder(
listenable: _bloc,
builder: (context, _) {
final state = _bloc.state;
if (state.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (state.errorMessage != null) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline, size: 48, color: colorScheme.error),
const SizedBox(height: 16),
FilledButton.tonal(
onPressed: _bloc.load,
child: const Text('重试'),
),
],
),
);
}
final categories = state.categories;
return Column(
children: [
// 分类选择器(横向滚动 Chips
SizedBox(
height: 48,
child: ListView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16),
children: categories.map((cat) {
final isSelected = cat == state.selectedCategory;
return Padding(
padding: const EdgeInsets.only(right: 8),
child: FilterChip(
selected: isSelected,
label: Text(cat),
onSelected: (_) => _bloc.selectCategory(cat),
selectedColor: colorScheme.primaryContainer,
checkmarkColor: colorScheme.primary,
),
);
}).toList(),
),
),
const SizedBox(height: 8),
// 贴纸包网格
Expanded(
child: state.filteredPacks.isEmpty
? const Center(child: Text('暂无贴纸包'))
: GridView.builder(
padding: const EdgeInsets.all(16),
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 0.85,
),
itemCount: state.filteredPacks.length,
itemBuilder: (context, index) {
return _StickerPackCard(
pack: state.filteredPacks[index],
colorScheme: colorScheme,
);
},
),
),
],
);
},
),
);
}
}
/// 贴纸包卡片
class _StickerPackCard extends StatelessWidget {
const _StickerPackCard({
required this.pack,
required this.colorScheme,
});
final StickerPack pack;
final ColorScheme colorScheme;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: AppRadius.mdBorder,
side: BorderSide(color: colorScheme.outlineVariant),
),
child: InkWell(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('打开贴纸包: ${pack.name}')),
);
},
borderRadius: AppRadius.mdBorder,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 贴纸包封面图标
Container(
width: 64,
height: 64,
decoration: BoxDecoration(
color: colorScheme.primaryContainer.withValues(alpha: 0.3),
borderRadius: AppRadius.mdBorder,
),
alignment: Alignment.center,
child: Text(
pack.coverImageUrl != null ? '🎨' : pack.displayCover,
style: const TextStyle(fontSize: 32),
),
),
const SizedBox(height: 12),
// 名称
Text(
pack.name,
style: theme.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w600,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
// 数量和价格标签
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'${pack.stickerCount}',
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.onSurface.withValues(alpha: 0.5),
),
),
if (!pack.isFree) ...[
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: AppColors.accent.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(6),
),
child: Text(
'积分',
style: theme.textTheme.labelSmall?.copyWith(
color: AppColors.accent,
),
),
),
],
],
),
],
),
),
),
);
}
}