Files
slogan/lib/features/outfit/plan_effect_page.dart
T
admin c4fd49bd7e feat(app): 穿搭生成 + 方案流 + 效果图 + 3D 化身查看
- outfit_provider:方案列表/生成任务轮询(pending→planning→scoring→rendering→done)/详情/选定主方案/收藏
- outfit_page:日期范围 + 地点生成表单,生成中显示任务进度,方案列表卡片
- plan_viewer_page:PageView 手指滑动切换方案,穿衣清单(衣橱/新品标记)、发型发色、设为主方案/收藏
- plan_effect_page:效果图 3 视角(正面/侧面/背面)查看
- avatar_viewer:three_dart + flutter_gl 3D 渲染实现(自动旋转),GLB 资产未接入时占位展示,kEnable3D 开关
- avatar_viewer_page:化身查看页 + 重新构建
- 新增 /plan-viewer /plan-effect /avatar-viewer 路由
2026-07-31 12:50:02 +08:00

121 lines
4.5 KiB
Dart

import 'package:flutter/material.dart';
import '../../core/config/app_config.dart';
import '../../shared/widgets/empty_view.dart';
import '../../shared/widgets/loading_view.dart';
import 'outfit_provider.dart';
const _angleLabels = {
'front': '正面',
'side': '侧面',
'back': '背面',
};
/// 效果图查看:3 视角(正面/侧面/背面)切换
class PlanEffectPage extends StatefulWidget {
final List<PlanEffectImageInfo> images;
const PlanEffectPage({super.key, required this.images});
@override
State<PlanEffectPage> createState() => _PlanEffectPageState();
}
class _PlanEffectPageState extends State<PlanEffectPage> {
String? _selected; // 当前角度,null = 展示全部
@override
Widget build(BuildContext context) {
final done = widget.images.where((i) => i.done).toList();
final pending = widget.images.where((i) => !i.done).toList();
final angles = widget.images.map((i) => i.angle).toSet();
return Scaffold(
appBar: AppBar(title: const Text('效果图')),
body: Column(
children: [
if (angles.length > 1)
SizedBox(
height: 48,
child: ListView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 12),
children: [
Padding(
padding: const EdgeInsets.only(right: 8),
child: ChoiceChip(
label: const Text('全部'),
selected: _selected == null,
onSelected: (_) => setState(() => _selected = null),
),
),
for (final a in angles)
Padding(
padding: const EdgeInsets.only(right: 8),
child: ChoiceChip(
label: Text(_angleLabels[a] ?? a),
selected: _selected == a,
onSelected: (_) => setState(() => _selected = a),
),
),
],
),
),
Expanded(
child: done.isEmpty
? (pending.isEmpty
? const EmptyView(
message: '暂无效果图,选定主方案后自动生成(每日限 3 次)')
: const LoadingView(text: '效果图生成中,请稍后刷新...'))
: GridView.builder(
padding: const EdgeInsets.all(12),
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 0.8,
),
itemCount: _selected == null
? done.length
: done.where((i) => i.angle == _selected).length,
itemBuilder: (ctx, idx) {
final shown = _selected == null
? done
: done.where((i) => i.angle == _selected).toList();
final img = shown[idx];
return Card(
clipBehavior: Clip.antiAlias,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: Image.network(
AppConfig.resolveUrl(img.url),
fit: BoxFit.cover,
errorBuilder: (_, _, _) => const Icon(
Icons.broken_image_outlined,
size: 48,
color: Colors.grey),
),
),
Padding(
padding: const EdgeInsets.all(8),
child: Text(_angleLabels[img.angle] ?? img.angle,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold)),
),
],
),
);
},
),
),
],
),
);
}
}