jjsos_autosos_flutter/lib/pages/order/complete_page.dart

317 lines
9.4 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import 'package:autosos_flutter/model/misc.dart';
import 'package:autosos_flutter/model/order.dart';
import 'package:autosos_flutter/model/payment.dart';
import 'package:autosos_flutter/provider/providers.dart';
import 'package:autosos_flutter/router/app_router.dart';
import 'package:autosos_flutter/util/toast.dart';
import 'package:autosos_flutter/widget/multi_state_view.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
/// 完成订单页(对应 Android FinishOrderActivity
/// 录入费用明细 → 提交完成 → 跳转记录页
class CompletePage extends ConsumerStatefulWidget {
final int orderId;
const CompletePage({super.key, required this.orderId});
@override
ConsumerState<CompletePage> createState() => _CompletePageState();
}
class _CompletePageState extends ConsumerState<CompletePage> {
MultiState _state = MultiState.loading;
Order? _order;
final List<AmountItem> _items = [];
final TextEditingController _remarkCtrl = TextEditingController();
bool _submitting = false;
@override
void initState() {
super.initState();
_load();
}
@override
void dispose() {
_remarkCtrl.dispose();
super.dispose();
}
Future<void> _load() async {
setState(() => _state = MultiState.loading);
try {
final resp = await ref.read(orderApiProvider).orderDetail(widget.orderId);
if (!mounted) return;
if (!resp.isSuccess || resp.data == null) {
setState(() => _state = MultiState.error);
return;
}
setState(() {
_order = resp.data;
// 默认添加一条基础服务费
if (_items.isEmpty) {
_items.add(AmountItem(
id: 0,
title: '基础服务费',
amount: resp.data!.amount,
type: 'income',
createdAt: DateTime.now(),
orderNo: resp.data!.orderNo,
));
}
_state = MultiState.success;
});
} catch (e) {
if (mounted) setState(() => _state = MultiState.error);
}
}
double get _total => _items.fold(0, (s, e) => s + e.amount);
Future<void> _addItem() async {
final added = await showDialog<AmountItem>(
context: context,
builder: (_) => const _AddAmountDialog(),
);
if (added != null) setState(() => _items.add(added));
}
Future<void> _submit() async {
if (_items.isEmpty) {
Toast.error('请至少添加一项费用明细');
return;
}
setState(() => _submitting = true);
EasyLoading.show(status: '提交中…');
try {
final r = await ref.read(orderServiceProvider).complete(
amountItems: _items,
remark: _remarkCtrl.text.trim().isEmpty
? null
: _remarkCtrl.text.trim(),
);
EasyLoading.dismiss();
if (r is CompleteOrderInfo) {
Toast.success('订单已完成');
if (mounted) {
// 通知服务端完成(已在 OrderService 中调用)
context.go(Routes.orderRecord);
}
}
} catch (e) {
EasyLoading.dismiss();
Toast.error('提交失败:$e');
} finally {
if (mounted) setState(() => _submitting = false);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('完成订单'),
actions: [
if (_order != null)
Padding(
padding: const EdgeInsets.only(right: 12),
child: Center(
child: Text(
'订单 ${_order!.orderNo}',
style: const TextStyle(fontSize: 12),
),
),
),
],
),
body: MultiStateView(
state: _state,
onRetry: _load,
child: _order == null
? const SizedBox.shrink()
: ListView(
padding: const EdgeInsets.all(12),
children: [
_summary(),
const SizedBox(height: 12),
_itemList(),
const SizedBox(height: 8),
OutlinedButton.icon(
onPressed: _addItem,
icon: const Icon(Icons.add),
label: const Text('添加费用项'),
),
const SizedBox(height: 12),
TextField(
controller: _remarkCtrl,
maxLines: 3,
decoration: const InputDecoration(
labelText: '备注',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
Row(
children: [
const Text('合计:',
style: TextStyle(fontSize: 16)),
Text(
'¥${_total.toStringAsFixed(2)}',
style: const TextStyle(
color: Colors.red,
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 12),
ElevatedButton(
onPressed: _submitting ? null : _submit,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
minimumSize: const Size.fromHeight(48),
),
child: Text(_submitting ? '提交中…' : '提交完成'),
),
],
),
),
);
}
Widget _summary() {
final o = _order!;
return Material(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(o.serviceType,
style:
const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 4),
if (o.address != null)
Text('地点:${o.address}',
style: TextStyle(color: Colors.grey.shade700, fontSize: 13)),
if (o.customerName != null)
Text('客户:${o.customerName!}',
style: TextStyle(color: Colors.grey.shade700, fontSize: 13)),
],
),
),
);
}
Widget _itemList() {
return Material(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
child: Column(
children: [
for (int i = 0; i < _items.length; i++)
ListTile(
title: Text(_items[i].title),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('¥${_items[i].amount.toStringAsFixed(2)}',
style: const TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
)),
IconButton(
icon: const Icon(Icons.delete_outline,
color: Colors.red),
onPressed: () =>
setState(() => _items.removeAt(i)),
),
],
),
),
],
),
);
}
}
/// 新增费用项弹窗
class _AddAmountDialog extends StatefulWidget {
const _AddAmountDialog();
@override
State<_AddAmountDialog> createState() => _AddAmountDialogState();
}
class _AddAmountDialogState extends State<_AddAmountDialog> {
final _title = TextEditingController();
final _amount = TextEditingController();
@override
void dispose() {
_title.dispose();
_amount.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('添加费用项'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: _title,
decoration: const InputDecoration(
labelText: '费用名称',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 8),
TextField(
controller: _amount,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
decoration: const InputDecoration(
labelText: '金额',
prefixText: '¥ ',
border: OutlineInputBorder(),
),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
ElevatedButton(
onPressed: () {
final t = _title.text.trim();
final a = double.tryParse(_amount.text.trim()) ?? 0;
if (t.isEmpty || a <= 0) {
Toast.error('请填写完整');
return;
}
Navigator.pop(
context,
AmountItem(
id: 0,
title: t,
amount: a,
type: 'income',
createdAt: DateTime.now(),
),
);
},
child: const Text('添加'),
),
],
);
}
}