refactor: 前端改 2 Tab(穿搭向导 + 我的入口)

- 主框架收敛为「穿搭」「我的」两个 tab:
  - 穿搭 = 造型方案制作向导:个人信息完整性检查(4 类照片 +
    身形参数,缺失引导去完善)+ 任务参数(日期范围/地点)+ 已有方案
  - 我的 = 个人信息管理入口:用户卡片(昵称/会员状态)+ 我的形象 /
    我的衣橱 / 会员中心入口 + 退出登录
- profile_page 移除退出登录(归「我的」页)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-07-31 16:25:36 +08:00
co-authored by Claude Opus 4.7
parent 30de9a71ce
commit 3e457b700a
4 changed files with 229 additions and 34 deletions
+130 -4
View File
@@ -6,9 +6,12 @@ import 'package:go_router/go_router.dart';
import '../../shared/widgets/empty_view.dart';
import '../../shared/widgets/error_view.dart';
import '../../shared/widgets/loading_view.dart';
import '../profile/body_provider.dart';
import '../profile/photo_upload_provider.dart';
import 'outfit_provider.dart';
/// 穿搭:生成表单 + 方案列表
/// 穿搭:造型方案制作向导
/// 输入 = 个人信息(照片/身形)+ 任务参数(日期范围/地点)→ AI 生成造型方案
class OutfitPage extends ConsumerStatefulWidget {
const OutfitPage({super.key});
@@ -76,10 +79,17 @@ class _OutfitPageState extends ConsumerState<OutfitPage> {
Widget build(BuildContext context) {
final plans = ref.watch(outfitPlanProvider);
final gen = ref.watch(generateProvider);
final photos = ref.watch(photoProvider);
final body = ref.watch(bodyProvider);
final scheme = Theme.of(context).colorScheme;
final uploadedTypes = photos.value?.map((p) => p.type).toSet() ?? <int>{};
final bodyReady = body.value != null && body.value!.height > 0;
final photoCount = PhotoType.all.where(uploadedTypes.contains).length;
final doneCount = photoCount + (bodyReady ? 1 : 0);
return Scaffold(
appBar: AppBar(title: const Text('穿搭方案')),
appBar: AppBar(title: const Text('造型穿搭')),
body: RefreshIndicator(
onRefresh: () => ref.read(outfitPlanProvider.notifier).refresh(),
child: ListView(
@@ -91,7 +101,77 @@ class _OutfitPageState extends ConsumerState<OutfitPage> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('生成穿搭方案',
Row(
children: [
Icon(Icons.face_retouching_natural,
color: scheme.primary),
const SizedBox(width: 8),
Expanded(
child: Text('个人信息(AI 生成依据)',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: scheme.primary)),
),
Chip(
label: Text('$doneCount/5'),
visualDensity: VisualDensity.compact,
),
],
),
const SizedBox(height: 8),
for (final type in PhotoType.all)
_StatusRow(
ok: uploadedTypes.contains(type),
text: '${PhotoType.labels[type]}照片',
trailingText:
uploadedTypes.contains(type) ? '已上传' : '未上传',
actionText:
uploadedTypes.contains(type) ? null : '去完善',
onAction: () => context.push('/photo-guide'),
),
_StatusRow(
ok: bodyReady,
text: '身形参数',
trailingText: bodyReady
? '${body.value!.height}cm · ${body.value!.weight}kg · 肤色${body.value!.skinTone}'
: '未设置',
actionText: bodyReady ? '调整' : '去设置',
onAction: () => context.push('/body-tune'),
),
const SizedBox(height: 8),
if (doneCount == 5)
Row(
children: [
const Icon(Icons.check_circle,
size: 16, color: Colors.green),
const SizedBox(width: 6),
Expanded(
child: Text(
'个人信息齐全,生成的方案将更贴合你的身材与气质',
style: const TextStyle(
fontSize: 12, color: Colors.green),
),
),
],
)
else
Text(
'完善个人信息后,AI 生成的造型方案将更贴合你的身材与气质',
style: const TextStyle(fontSize: 12, color: Colors.grey),
),
],
),
),
),
const SizedBox(height: 12),
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('任务参数',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
@@ -137,7 +217,7 @@ class _OutfitPageState extends ConsumerState<OutfitPage> {
onPressed: _submitting ? null : _generate,
style: FilledButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14)),
child: const Text('生成'),
child: const Text('生成穿搭方案'),
),
],
),
@@ -183,6 +263,52 @@ class _OutfitPageState extends ConsumerState<OutfitPage> {
}
}
class _StatusRow extends StatelessWidget {
final bool ok;
final String text;
final String trailingText;
final String? actionText;
final VoidCallback? onAction;
const _StatusRow({
required this.ok,
required this.text,
required this.trailingText,
this.actionText,
this.onAction,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
Icon(
ok ? Icons.check_circle : Icons.radio_button_unchecked,
size: 16,
color: ok ? Colors.green : Colors.grey,
),
const SizedBox(width: 8),
Expanded(
child: Text(text, style: const TextStyle(fontSize: 13)),
),
Text(trailingText,
style: const TextStyle(fontSize: 12, color: Colors.grey)),
if (actionText != null)
TextButton(
onPressed: onAction,
style: TextButton.styleFrom(
visualDensity: VisualDensity.compact,
padding: const EdgeInsets.symmetric(horizontal: 8)),
child: Text(actionText!),
),
],
),
);
}
}
class _PlanCard extends ConsumerWidget {
final OutfitPlanInfo plan;