fix: web 端会员页崩溃(dart:io 不可用)+ 嵌套 AppBar 标题重复

- member_center_page 改用 kIsWeb + defaultTargetPlatform 判断 iOS,
  删 dart:io 依赖(web 访问 Platform 抛异常导致页面灰屏)
- HomePage 移除外层 AppBar,4 个 tab 页各自持有单一标题栏

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-07-31 16:11:08 +08:00
co-authored by Claude Opus 4.7
parent 5dda43a530
commit 30de9a71ce
2 changed files with 97 additions and 95 deletions
-3
View File
@@ -21,8 +21,6 @@ class _HomePageState extends ConsumerState<HomePage> {
int _index = 0;
bool _splashShown = false;
static const _titles = ['我的形象', '我的衣橱', '穿搭方案', '会员中心'];
@override
void initState() {
super.initState();
@@ -44,7 +42,6 @@ class _HomePageState extends ConsumerState<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(_titles[_index])),
body: IndexedStack(
index: _index,
children: const [
+97 -92
View File
@@ -1,5 +1,4 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fluttertoast/fluttertoast.dart';
@@ -69,7 +68,9 @@ class _MemberCenterPageState extends ConsumerState<MemberCenterPage> {
int? _typeFilter;
bool _rewarding = false;
bool get _isIOS => Platform.isIOS;
// web 上无 Platformdart:io 不可用),且 web 端默认走系统浏览器收银台
bool get _isIOS =>
!kIsWeb && defaultTargetPlatform == TargetPlatform.iOS;
Future<void> _openPlans() async {
final plans = await showModalBottomSheet<MemberPlan>(
@@ -121,101 +122,105 @@ class _MemberCenterPageState extends ConsumerState<MemberCenterPage> {
final member = ref.watch(memberProvider);
final stores = ref.watch(storeProvider);
final scheme = Theme.of(context).colorScheme;
return ListView(
padding: const EdgeInsets.all(16),
children: [
_MemberCard(
member: member,
isIOS: _isIOS,
onOpenPlans: _openPlans,
),
const SizedBox(height: 12),
const _RecentDealsCard(),
if (member.value?.isVip == false) ...[
const SizedBox(height: 12),
_AdsRewardCard(
rewarding: _rewarding,
onClaim: (adType, msg) => _claimReward(adType, msg),
return Scaffold(
appBar: AppBar(title: const Text('会员中心')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
_MemberCard(
member: member,
isIOS: _isIOS,
onOpenPlans: _openPlans,
),
],
const SizedBox(height: 16),
Row(
children: [
Text('合作门店',
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold)),
const Spacer(),
ChoiceChip(
label: const Text('全部'),
selected: _typeFilter == null,
onSelected: (_) => setState(() => _typeFilter = null),
const SizedBox(height: 12),
const _RecentDealsCard(),
if (member.value?.isVip == false) ...[
const SizedBox(height: 12),
_AdsRewardCard(
rewarding: _rewarding,
onClaim: (adType, msg) => _claimReward(adType, msg),
),
const SizedBox(width: 8),
for (final entry in _storeTypeLabels.entries) ...[
],
const SizedBox(height: 16),
Row(
children: [
Text('合作门店',
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold)),
const Spacer(),
ChoiceChip(
label: Text(entry.value),
selected: _typeFilter == entry.key,
onSelected: (_) => setState(() => _typeFilter = entry.key),
label: const Text('全部'),
selected: _typeFilter == null,
onSelected: (_) => setState(() => _typeFilter = null),
),
const SizedBox(width: 8),
],
],
),
const SizedBox(height: 8),
stores.when(
loading: () => const Padding(
padding: EdgeInsets.only(top: 32), child: LoadingView(text: '加载门店...')),
error: (e, _) => Padding(
padding: const EdgeInsets.only(top: 32),
child: ErrorView(
message: e.toString().replaceFirst('Exception: ', ''),
onRetry: () => ref.read(storeProvider.notifier).refresh(),
),
),
data: (list) {
final shown = _typeFilter == null
? list
: list.where((s) => s.type == _typeFilter).toList();
if (shown.isEmpty) {
return const Padding(
padding: EdgeInsets.only(top: 32),
child: Center(
child: Text('附近暂无可合作门店',
style: TextStyle(color: Colors.grey))),
);
}
return Column(
children: [
for (final s in shown) ...[
Card(
child: ListTile(
leading: CircleAvatar(
backgroundColor: scheme.primaryContainer,
child: Icon(
s.type == 1 ? Icons.content_cut : Icons.checkroom),
),
title: Text(s.name),
subtitle: Text('${s.address}\n${s.commissionPolicy}'),
isThreeLine: true,
trailing: member.value?.isVip == true
? const Chip(
label: Text('会员价'),
labelStyle: TextStyle(
color: Colors.white, fontSize: 10),
backgroundColor: Colors.amber,
visualDensity: VisualDensity.compact,
padding: EdgeInsets.zero,
)
: const Icon(Icons.chevron_right,
color: Colors.grey),
),
),
const SizedBox(height: 8),
],
for (final entry in _storeTypeLabels.entries) ...[
ChoiceChip(
label: Text(entry.value),
selected: _typeFilter == entry.key,
onSelected: (_) => setState(() => _typeFilter = entry.key),
),
const SizedBox(width: 8),
],
);
},
),
],
],
),
const SizedBox(height: 8),
stores.when(
loading: () => const Padding(
padding: EdgeInsets.only(top: 32),
child: LoadingView(text: '加载门店...')),
error: (e, _) => Padding(
padding: const EdgeInsets.only(top: 32),
child: ErrorView(
message: e.toString().replaceFirst('Exception: ', ''),
onRetry: () => ref.read(storeProvider.notifier).refresh(),
),
),
data: (list) {
final shown = _typeFilter == null
? list
: list.where((s) => s.type == _typeFilter).toList();
if (shown.isEmpty) {
return const Padding(
padding: EdgeInsets.only(top: 32),
child: Center(
child: Text('附近暂无可合作门店',
style: TextStyle(color: Colors.grey))),
);
}
return Column(
children: [
for (final s in shown) ...[
Card(
child: ListTile(
leading: CircleAvatar(
backgroundColor: scheme.primaryContainer,
child: Icon(
s.type == 1 ? Icons.content_cut : Icons.checkroom),
),
title: Text(s.name),
subtitle: Text('${s.address}\n${s.commissionPolicy}'),
isThreeLine: true,
trailing: member.value?.isVip == true
? const Chip(
label: Text('会员价'),
labelStyle: TextStyle(
color: Colors.white, fontSize: 10),
backgroundColor: Colors.amber,
visualDensity: VisualDensity.compact,
padding: EdgeInsets.zero,
)
: const Icon(Icons.chevron_right,
color: Colors.grey),
),
),
const SizedBox(height: 8),
],
],
);
},
),
],
),
);
}
}