import 'dart:convert'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:image_picker/image_picker.dart'; import 'package:uuid/uuid.dart'; import 'package:vitalapp/architecture/storage/storage.dart'; import 'package:vitalapp/components/appbar.dart'; import 'package:fis_common/logger/logger.dart'; import 'package:vitalapp/rpc.dart'; import 'dart:ui' as ui; import 'package:universal_html/html.dart' as html; import 'controller.dart'; /// 签字板 class SignatureBoardPage extends GetView { SignatureBoardPage({super.key}); final canvasController = _SignatureBoardController(); @override Widget build(BuildContext context) { final params = Get.parameters; final String title = params["title"] ?? "设置签名"; return WillPopScope( onWillPop: () async { return false; }, child: Scaffold( appBar: VAppBar( context: context, titleText: title, ), body: Stack( children: [ _SignatureBoard(controller: canvasController), Positioned( right: 32, bottom: 0, top: 0, child: _buildFloatActions(context), ), ], ), ), ); } Widget _buildFloatActions(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ SizedBox( width: 90, height: 90, child: ElevatedButton( style: ButtonStyle( foregroundColor: const MaterialStatePropertyAll(Colors.white), backgroundColor: const MaterialStatePropertyAll(Colors.grey), shape: MaterialStatePropertyAll( RoundedRectangleBorder( borderRadius: BorderRadius.circular(45), side: BorderSide.none, ), ), ), onPressed: () { canvasController.revocation(); }, child: const Text( "撤销", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), ), ), const SizedBox(height: 32), SizedBox( width: 90, height: 90, child: ElevatedButton( style: ButtonStyle( foregroundColor: const MaterialStatePropertyAll(Colors.white), backgroundColor: const MaterialStatePropertyAll(Colors.red), shape: MaterialStatePropertyAll( RoundedRectangleBorder( borderRadius: BorderRadius.circular(45), side: BorderSide.none, ), ), ), onPressed: () { canvasController.clear(); }, child: const Text( "清除", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), ), ), const SizedBox(height: 32), SizedBox( width: 90, height: 90, child: ElevatedButton( style: ButtonStyle( foregroundColor: const MaterialStatePropertyAll(Colors.white), backgroundColor: MaterialStatePropertyAll(Theme.of(context).primaryColor), shape: MaterialStatePropertyAll( RoundedRectangleBorder( borderRadius: BorderRadius.circular(45), side: BorderSide.none, ), ), ), onPressed: () async { final result = await canvasController.getImageUrl(); Get.back(result: result); }, child: const Text( "保存", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), ), ), ], ); } } class _SignatureBoardController extends ChangeNotifier { List points = []; SignaturePainter? painter; Size? size; /// 清除 void clear() { points = []; notifyListeners(); } /// 撤销 void revocation() { int index = points.lastIndexOf(null); if (index == points.length - 1) { points = points.take(index).toList(); index = points.lastIndexOf(null); } if (index < 0) { points = []; } else { points = points.take(index).toList(); } notifyListeners(); } XFile? convertBase64ToXFile(String base64Image) { try { final bytes = base64Decode(base64Image); final tempDir = Directory.systemTemp; final tempPath = tempDir.path; final imageId = const Uuid().v4().replaceAll('-', ''); final filePath = '$tempPath/$imageId'; File(filePath).writeAsBytesSync(bytes); return XFile(filePath); } catch (e) { print('Error converting base64 to XFile: $e'); return null; } } html.File? convertBase64ToFile(String base64Image) { try { final bytes = base64Decode(base64Image); const mimeType = 'image/jpeg'; // 替换为您的图片类型 final blob = html.Blob([bytes], mimeType); final file = html.File([blob], 'image.jpg'); // 替换为您的文件名 return file; } catch (e) { print('Error converting base64 to File: $e'); return null; } } Future getImageUrl() async { final imageBase64 = await getImageBase64(); String? url; if (kIsWeb) { final file = convertBase64ToFile(imageBase64!); url = await rpc.storage.webUpload(file!); } else { final xFile = convertBase64ToXFile(imageBase64!); url = await rpc.storage.upload(xFile!); } return url; } /// 获取图片base64字符串 Future getImageBase64() async { try { final recorder = ui.PictureRecorder(); final canvas = Canvas(recorder); painter!.paint(canvas, size!); // Paint paint = Paint() // ..color = Colors.black // ..strokeCap = StrokeCap.round // ..strokeWidth = 8.0; // for (int i = 0; i < points.length - 1; i++) { // if (points[i] != null && points[i + 1] != null) { // canvas.drawLine(points[i]!, points[i + 1]!, paint); // } // } // 将绘制的内容转换为图像 final picture = recorder.endRecording(); final image = await picture.toImage(size!.width.toInt(), size!.height.toInt()); // 将图像保存到文件 final byteData = await image.toByteData(format: ui.ImageByteFormat.png); if (byteData != null) { String base64Image = base64Encode(byteData.buffer.asUint8List()); return base64Image; } } catch (e) { logger.e("_SignatureBoardController getImageBase64 error.", e); } return null; } } class _SignatureBoard extends StatefulWidget { final _SignatureBoardController controller; const _SignatureBoard({required this.controller}); @override _SignatureBoardState createState() => _SignatureBoardState(); } class _SignatureBoardState extends State<_SignatureBoard> { List get _points => widget.controller.points; @override void initState() { widget.controller.addListener(_onUpdate); super.initState(); } @override void dispose() { widget.controller.removeListener(_onUpdate); super.dispose(); } void _onUpdate() { setState(() {}); } @override Widget build(BuildContext context) { return GestureDetector( onPanUpdate: (DragUpdateDetails details) { setState(() { RenderBox renderBox = context.findRenderObject() as RenderBox; Offset localPosition = renderBox.globalToLocal(details.globalPosition); widget.controller.points = List.from(_points)..add(localPosition); }); }, onPanEnd: (DragEndDetails details) { widget.controller.points.add(null); }, child: LayoutBuilder(builder: (context, c) { final size = Size(c.maxWidth, c.maxHeight); final painter = SignaturePainter(points: _points); widget.controller.size = size; widget.controller.painter = painter; return CustomPaint( painter: painter, size: size, ); }), ); } } class SignaturePainter extends CustomPainter { List points; SignaturePainter({required this.points}); @override void paint(Canvas canvas, Size size) { Paint paint = Paint() ..color = Colors.black ..strokeCap = StrokeCap.round ..strokeWidth = 8.0; for (int i = 0; i < points.length - 1; i++) { if (points[i] != null && points[i + 1] != null) { canvas.drawLine(points[i]!, points[i + 1]!, paint); } } } @override bool shouldRepaint(SignaturePainter oldDelegate) => true; }