feat: add Flutter mobile clients and staff delivery API

This commit is contained in:
david
2026-07-30 21:47:41 +08:00
parent 550efb3812
commit 36a5ced1c0
228 changed files with 17159 additions and 22 deletions

View 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)),
],
),
);
},
),
);
}

View File

@@ -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();
}
}
}