feat: add Flutter web build support
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
@@ -44,12 +42,17 @@ class _EvidencePageState extends State<EvidencePage> {
|
||||
}
|
||||
|
||||
Future<void> _restore() async {
|
||||
final draft = await widget.drafts.readDraft(widget.session.identity, widget.taskIdentity);
|
||||
final draft = await widget.drafts.readDraft(
|
||||
widget.session.identity,
|
||||
widget.taskIdentity,
|
||||
);
|
||||
if (draft == null || !mounted) return;
|
||||
final values = draft['evidence'];
|
||||
if (values is List) {
|
||||
for (final value in values.whereType<Map<Object?, Object?>>()) {
|
||||
final mapped = value.map<String, Object?>((key, item) => MapEntry(key.toString(), item));
|
||||
final mapped = value.map<String, Object?>(
|
||||
(key, item) => MapEntry(key.toString(), item),
|
||||
);
|
||||
final stage = mapped['stage'] as String? ?? '';
|
||||
if (stage.isNotEmpty) _evidence[stage] = mapped;
|
||||
}
|
||||
@@ -70,7 +73,7 @@ class _EvidencePageState extends State<EvidencePage> {
|
||||
accountIdentity: widget.session.identity,
|
||||
taskIdentity: widget.taskIdentity,
|
||||
stage: stage,
|
||||
sourcePath: image.path,
|
||||
source: image,
|
||||
);
|
||||
_evidence[stage] = {
|
||||
'stage': stage,
|
||||
@@ -97,25 +100,27 @@ class _EvidencePageState extends State<EvidencePage> {
|
||||
|
||||
Future<void> _submit() async {
|
||||
if (!_requiredStages.every(_evidence.containsKey) || _result.text.trim().isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('请完成结果说明和全部必需取证项')));
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('请完成结果说明和全部必需取证项')));
|
||||
return;
|
||||
}
|
||||
setState(() => _busy = true);
|
||||
final temporaryFiles = <String>[];
|
||||
final temporaryFiles = <XFile>[];
|
||||
try {
|
||||
await _save();
|
||||
final inputs = <EvidenceInput>[];
|
||||
for (final item in _evidence.values) {
|
||||
final path = await widget.drafts.materializeAttachment(
|
||||
final file = await widget.drafts.materializeAttachment(
|
||||
accountIdentity: widget.session.identity,
|
||||
sealedName: item['sealed_name'] as String,
|
||||
);
|
||||
temporaryFiles.add(path);
|
||||
temporaryFiles.add(file);
|
||||
inputs.add(
|
||||
EvidenceInput(
|
||||
evidenceType: item['stage'] as String,
|
||||
mediaType: item['media_type'] as String,
|
||||
filePath: path,
|
||||
file: file,
|
||||
capturedAt: DateTime.parse(item['captured_at'] as String),
|
||||
requestNo: item['request_no'] as String,
|
||||
),
|
||||
@@ -127,16 +132,20 @@ class _EvidencePageState extends State<EvidencePage> {
|
||||
conclusion: _conclusion,
|
||||
evidence: inputs,
|
||||
);
|
||||
await widget.drafts.deleteDraft(widget.session.identity, widget.taskIdentity);
|
||||
await widget.drafts.deleteDraft(
|
||||
widget.session.identity,
|
||||
widget.taskIdentity,
|
||||
);
|
||||
if (mounted) Navigator.pop(context, true);
|
||||
} catch (error) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('提交失败,草稿仍安全保留:$error')));
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('提交失败,草稿仍安全保留:$error')));
|
||||
}
|
||||
} finally {
|
||||
for (final path in temporaryFiles) {
|
||||
final file = File(path);
|
||||
if (file.existsSync()) await file.delete();
|
||||
for (final file in temporaryFiles) {
|
||||
await widget.drafts.discardMaterializedAttachment(file);
|
||||
}
|
||||
if (mounted) setState(() => _busy = false);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
@@ -42,14 +40,19 @@ class _WorkDetailPageState extends State<WorkDetailPage> {
|
||||
? widget.repository.deliveryDetail(widget.identity)
|
||||
: widget.repository.ticketDetail(widget.identity);
|
||||
|
||||
Future<void> _run(Future<void> Function(WorkItem) action, WorkItem item) async {
|
||||
Future<void> _run(
|
||||
Future<void> Function(WorkItem) action,
|
||||
WorkItem item,
|
||||
) async {
|
||||
setState(() => _busy = true);
|
||||
try {
|
||||
await action(item);
|
||||
setState(() => _future = _load());
|
||||
} catch (error) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(error.toString())));
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(error.toString())));
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _busy = false);
|
||||
@@ -68,7 +71,10 @@ class _WorkDetailPageState extends State<WorkDetailPage> {
|
||||
decoration: const InputDecoration(labelText: '原因'),
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(context), child: const Text('取消')),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('取消'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(context, controller.text.trim()),
|
||||
child: const Text('确认'),
|
||||
@@ -81,7 +87,10 @@ class _WorkDetailPageState extends State<WorkDetailPage> {
|
||||
}
|
||||
|
||||
Future<void> _receipt(WorkItem item) async {
|
||||
final existing = await widget.drafts.readDraft(widget.session.identity, item.identity);
|
||||
final existing = await widget.drafts.readDraft(
|
||||
widget.session.identity,
|
||||
item.identity,
|
||||
);
|
||||
if (!mounted) return;
|
||||
String? sealedName;
|
||||
if (existing?['kind'] == 'delivery_receipt' && existing?['sealed_name'] is String) {
|
||||
@@ -91,25 +100,34 @@ class _WorkDetailPageState extends State<WorkDetailPage> {
|
||||
title: const Text('发现未提交签收草稿'),
|
||||
content: const Text('是否继续提交上次加密保存的签收凭证?'),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('重新拍摄')),
|
||||
FilledButton(onPressed: () => Navigator.pop(context, true), child: const Text('继续提交')),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: const Text('重新拍摄'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: const Text('继续提交'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (reuse == true) sealedName = existing!['sealed_name'] as String;
|
||||
}
|
||||
if (sealedName == null) {
|
||||
final image = await ImagePicker().pickImage(source: ImageSource.camera, imageQuality: 82);
|
||||
final image = await ImagePicker().pickImage(
|
||||
source: ImageSource.camera,
|
||||
imageQuality: 82,
|
||||
);
|
||||
if (image == null) return;
|
||||
sealedName = await widget.drafts.sealAttachment(
|
||||
accountIdentity: widget.session.identity,
|
||||
taskIdentity: item.identity,
|
||||
stage: 'receipt',
|
||||
sourcePath: image.path,
|
||||
source: image,
|
||||
);
|
||||
}
|
||||
setState(() => _busy = true);
|
||||
String? temporaryPath;
|
||||
XFile? temporaryFile;
|
||||
try {
|
||||
await widget.drafts.saveDraft(
|
||||
accountIdentity: widget.session.identity,
|
||||
@@ -122,7 +140,7 @@ class _WorkDetailPageState extends State<WorkDetailPage> {
|
||||
'captured_at': DateTime.now().toUtc().toIso8601String(),
|
||||
},
|
||||
);
|
||||
temporaryPath = await widget.drafts.materializeAttachment(
|
||||
temporaryFile = await widget.drafts.materializeAttachment(
|
||||
accountIdentity: widget.session.identity,
|
||||
sealedName: sealedName,
|
||||
);
|
||||
@@ -130,7 +148,7 @@ class _WorkDetailPageState extends State<WorkDetailPage> {
|
||||
identity: item.identity,
|
||||
recipientName: item.raw['contact_name'] as String? ?? '收货人',
|
||||
recipientPhone: item.raw['contact_phone'] as String? ?? '',
|
||||
proofFile: temporaryPath,
|
||||
proofFile: temporaryFile,
|
||||
);
|
||||
await widget.drafts.deleteDraft(widget.session.identity, item.identity);
|
||||
if (mounted) setState(() => _future = _load());
|
||||
@@ -141,9 +159,8 @@ class _WorkDetailPageState extends State<WorkDetailPage> {
|
||||
).showSnackBar(SnackBar(content: Text('签收提交失败,加密草稿已保留:$error')));
|
||||
}
|
||||
} finally {
|
||||
if (temporaryPath != null) {
|
||||
final file = File(temporaryPath);
|
||||
if (file.existsSync()) await file.delete();
|
||||
if (temporaryFile != null) {
|
||||
await widget.drafts.discardMaterializedAttachment(temporaryFile);
|
||||
}
|
||||
if (mounted) setState(() => _busy = false);
|
||||
}
|
||||
@@ -170,7 +187,10 @@ class _WorkDetailPageState extends State<WorkDetailPage> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(item.number, style: const TextStyle(color: Colors.white70)),
|
||||
Text(
|
||||
item.number,
|
||||
style: const TextStyle(color: Colors.white70),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
item.title,
|
||||
@@ -206,7 +226,10 @@ class _WorkDetailPageState extends State<WorkDetailPage> {
|
||||
onPressed: _busy
|
||||
? null
|
||||
: () => _run(
|
||||
(value) => widget.repository.start(value, widget.session.roleCode),
|
||||
(value) => widget.repository.start(
|
||||
value,
|
||||
widget.session.roleCode,
|
||||
),
|
||||
item,
|
||||
),
|
||||
child: const Text('开始处理'),
|
||||
@@ -216,7 +239,9 @@ class _WorkDetailPageState extends State<WorkDetailPage> {
|
||||
onPressed: _busy
|
||||
? null
|
||||
: () => _run(
|
||||
(value) => widget.repository.appendCurrentTrack(value.identity),
|
||||
(value) => widget.repository.appendCurrentTrack(
|
||||
value.identity,
|
||||
),
|
||||
item,
|
||||
),
|
||||
child: const Text('上报当前位置'),
|
||||
@@ -225,7 +250,10 @@ class _WorkDetailPageState extends State<WorkDetailPage> {
|
||||
ElevatedButton(
|
||||
onPressed: _busy
|
||||
? null
|
||||
: () => _run((value) => widget.repository.arrive(value.identity), item),
|
||||
: () => _run(
|
||||
(value) => widget.repository.arrive(value.identity),
|
||||
item,
|
||||
),
|
||||
child: const Text('到达并校验围栏'),
|
||||
),
|
||||
if (item.allowedActions.contains('submit_receipt'))
|
||||
@@ -253,8 +281,11 @@ class _WorkDetailPageState extends State<WorkDetailPage> {
|
||||
final reason = await _reason();
|
||||
if (reason != null && reason.isNotEmpty) {
|
||||
await _run(
|
||||
(value) =>
|
||||
widget.repository.exception(value, widget.session.roleCode, reason),
|
||||
(value) => widget.repository.exception(
|
||||
value,
|
||||
widget.session.roleCode,
|
||||
reason,
|
||||
),
|
||||
item,
|
||||
);
|
||||
}
|
||||
@@ -269,8 +300,11 @@ class _WorkDetailPageState extends State<WorkDetailPage> {
|
||||
final reason = await _reason();
|
||||
if (reason != null && reason.isNotEmpty) {
|
||||
await _run(
|
||||
(value) =>
|
||||
widget.repository.recover(value, widget.session.roleCode, reason),
|
||||
(value) => widget.repository.recover(
|
||||
value,
|
||||
widget.session.roleCode,
|
||||
reason,
|
||||
),
|
||||
item,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user