feat: complete leave approval MVP
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
import 'package:aioa_mobile/features/form/application/leave_attachment_controller.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class LeaveAttachmentSheet extends ConsumerStatefulWidget {
|
||||
const LeaveAttachmentSheet({required this.leaveRequestId, super.key});
|
||||
|
||||
final String leaveRequestId;
|
||||
|
||||
@override
|
||||
ConsumerState<LeaveAttachmentSheet> createState() =>
|
||||
_LeaveAttachmentSheetState();
|
||||
}
|
||||
|
||||
class _LeaveAttachmentSheetState extends ConsumerState<LeaveAttachmentSheet> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
Future.microtask(
|
||||
() => ref
|
||||
.read(leaveAttachmentProvider(widget.leaveRequestId).notifier)
|
||||
.load(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final state = ref.watch(leaveAttachmentProvider(widget.leaveRequestId));
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
20,
|
||||
0,
|
||||
20,
|
||||
20 + MediaQuery.viewInsetsOf(context).bottom,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
'添加附件',
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
const Text('支持 JPEG、PNG、PDF,单个文件不超过 10 MB。文件将直接上传至对象存储。'),
|
||||
if (state.error != null) ...[
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
state.error!,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
||||
),
|
||||
],
|
||||
if (state.uploading) ...[
|
||||
const SizedBox(height: 14),
|
||||
LinearProgressIndicator(value: state.progress),
|
||||
const SizedBox(height: 6),
|
||||
Text('正在上传 ${(state.progress * 100).round()}%'),
|
||||
],
|
||||
if (state.items.isNotEmpty) ...[
|
||||
const SizedBox(height: 14),
|
||||
for (final item in state.items)
|
||||
ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: Icon(
|
||||
item.contentType == 'application/pdf'
|
||||
? Icons.picture_as_pdf_outlined
|
||||
: Icons.image_outlined,
|
||||
),
|
||||
title: Text(
|
||||
item.fileName,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
subtitle: Text(
|
||||
'${_formatBytes(item.sizeBytes)} · ${item.status == 'READY' ? '已上传' : '处理中'}',
|
||||
),
|
||||
trailing: item.status == 'READY'
|
||||
? const Icon(Icons.check_circle, color: Colors.green)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 14),
|
||||
OutlinedButton.icon(
|
||||
onPressed: state.uploading ? null : _pickAndUpload,
|
||||
icon: const Icon(Icons.attach_file),
|
||||
label: const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 12),
|
||||
child: Text('选择附件'),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
FilledButton(
|
||||
onPressed: state.uploading ? null : () => Navigator.pop(context),
|
||||
child: const Text('完成'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _pickAndUpload() async {
|
||||
final result = await FilePicker.platform.pickFiles(
|
||||
type: FileType.custom,
|
||||
allowedExtensions: const ['jpg', 'jpeg', 'png', 'pdf'],
|
||||
withData: true,
|
||||
);
|
||||
if (result == null || !mounted) return;
|
||||
final file = result.files.single;
|
||||
final bytes = file.bytes;
|
||||
if (bytes == null) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('无法读取所选文件')));
|
||||
return;
|
||||
}
|
||||
final contentType = switch (file.extension?.toLowerCase()) {
|
||||
'jpg' || 'jpeg' => 'image/jpeg',
|
||||
'png' => 'image/png',
|
||||
'pdf' => 'application/pdf',
|
||||
_ => null,
|
||||
};
|
||||
if (contentType == null) return;
|
||||
await ref
|
||||
.read(leaveAttachmentProvider(widget.leaveRequestId).notifier)
|
||||
.upload(fileName: file.name, contentType: contentType, bytes: bytes);
|
||||
}
|
||||
|
||||
String _formatBytes(int bytes) {
|
||||
if (bytes < 1024) return '$bytes B';
|
||||
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
|
||||
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user