feat: add Flutter mobile clients and staff delivery API
This commit is contained in:
86
apps/user_app/lib/ui/features/shared/record_list_page.dart
Normal file
86
apps/user_app/lib/ui/features/shared/record_list_page.dart
Normal file
@@ -0,0 +1,86 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../core/widgets.dart';
|
||||
import 'record_list_view_model.dart';
|
||||
|
||||
class RecordListPage extends StatefulWidget {
|
||||
const RecordListPage({
|
||||
required this.title,
|
||||
required this.eyebrow,
|
||||
required this.viewModel,
|
||||
this.description,
|
||||
this.floatingActionButton,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String eyebrow;
|
||||
final String? description;
|
||||
final RecordListViewModel viewModel;
|
||||
final Widget? floatingActionButton;
|
||||
|
||||
@override
|
||||
State<RecordListPage> createState() => _RecordListPageState();
|
||||
}
|
||||
|
||||
class _RecordListPageState extends State<RecordListPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.viewModel.load();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.viewModel.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
appBar: AppBar(title: Text(widget.title)),
|
||||
floatingActionButton: widget.floatingActionButton,
|
||||
body: ListenableBuilder(
|
||||
listenable: widget.viewModel,
|
||||
builder: (context, _) {
|
||||
final state = widget.viewModel;
|
||||
if (state.loading && state.records.isEmpty) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (state.error != null && state.records.isEmpty) {
|
||||
return EmptyState(
|
||||
title: '加载失败',
|
||||
description: state.error.toString(),
|
||||
onRetry: state.load,
|
||||
);
|
||||
}
|
||||
return RefreshIndicator(
|
||||
onRefresh: state.load,
|
||||
child: CustomScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: PageIntro(
|
||||
eyebrow: widget.eyebrow,
|
||||
title: widget.title,
|
||||
description: widget.description,
|
||||
),
|
||||
),
|
||||
if (state.records.isEmpty)
|
||||
const SliverFillRemaining(
|
||||
hasScrollBody: false,
|
||||
child: EmptyState(title: '暂无记录', description: '服务端还没有可展示的数据'),
|
||||
)
|
||||
else
|
||||
SliverList.builder(
|
||||
itemCount: state.records.length,
|
||||
itemBuilder: (context, index) => RecordCard(record: state.records[index]),
|
||||
),
|
||||
const SliverPadding(padding: EdgeInsets.only(bottom: 24)),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../../../domain/models/client_models.dart';
|
||||
|
||||
typedef RecordLoader = Future<List<ClientRecord>> Function();
|
||||
|
||||
class RecordListViewModel extends ChangeNotifier {
|
||||
RecordListViewModel(this._loader);
|
||||
|
||||
final RecordLoader _loader;
|
||||
List<ClientRecord> _records = const [];
|
||||
Object? _error;
|
||||
bool _loading = false;
|
||||
|
||||
List<ClientRecord> get records => List.unmodifiable(_records);
|
||||
Object? get error => _error;
|
||||
bool get loading => _loading;
|
||||
|
||||
Future<void> load() async {
|
||||
_loading = true;
|
||||
_error = null;
|
||||
notifyListeners();
|
||||
try {
|
||||
_records = await _loader();
|
||||
} catch (error) {
|
||||
_error = error;
|
||||
} finally {
|
||||
_loading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user