ver1.00.00
bindQR
This commit is contained in:
322
lib/pages/score/updateScorePage.dart
Normal file
322
lib/pages/score/updateScorePage.dart
Normal file
@@ -0,0 +1,322 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:google_mlkit_barcode_scanning/google_mlkit_barcode_scanning.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../providers/user_provider.dart';
|
||||
|
||||
class UpdateScorePage extends StatefulWidget {
|
||||
const UpdateScorePage({super.key});
|
||||
|
||||
@override
|
||||
State<UpdateScorePage> createState() => _UpdateScorePageState();
|
||||
}
|
||||
|
||||
class _UpdateScorePageState extends State<UpdateScorePage> {
|
||||
final TextEditingController _segaIdController = TextEditingController();
|
||||
final ImagePicker _picker = ImagePicker();
|
||||
|
||||
bool _isProcessing = false;
|
||||
String? _statusMessage;
|
||||
File? _selectedImageFile; // 用于预览
|
||||
|
||||
// 条码扫描器 (支持 QR Code, Data Matrix, Aztec 等)
|
||||
final BarcodeScanner _barcodeScanner = BarcodeScanner(
|
||||
formats: [BarcodeFormat.qrCode, BarcodeFormat.dataMatrix, BarcodeFormat.aztec],
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_segaIdController.dispose();
|
||||
_barcodeScanner.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// 1. 选择图片并识别二维码
|
||||
Future<void> _pickImageAndScan() async {
|
||||
try {
|
||||
final XFile? pickedFile = await _picker.pickImage(source: ImageSource.gallery);
|
||||
if (pickedFile == null) return;
|
||||
|
||||
setState(() {
|
||||
_isProcessing = true;
|
||||
_statusMessage = "正在识别二维码...";
|
||||
_selectedImageFile = File(pickedFile.path);
|
||||
});
|
||||
|
||||
// 执行二维码识别
|
||||
final inputImage = InputImage.fromFilePath(pickedFile.path);
|
||||
final List<Barcode> barcodes = await _barcodeScanner.processImage(inputImage);
|
||||
|
||||
setState(() {
|
||||
_isProcessing = false;
|
||||
});
|
||||
|
||||
if (barcodes.isNotEmpty) {
|
||||
// 取第一个识别到的二维码内容
|
||||
final String code = barcodes.first.rawValue ?? "";
|
||||
|
||||
if (code.isNotEmpty) {
|
||||
setState(() {
|
||||
_segaIdController.text = code;
|
||||
_statusMessage = "识别成功: $code";
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_statusMessage = "二维码内容为空";
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setState(() {
|
||||
_statusMessage = "未在图中发现二维码,请手动输入";
|
||||
});
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_isProcessing = false;
|
||||
_statusMessage = "识别失败: $e";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 执行隐写上传
|
||||
Future<void> _handleUpload() async {
|
||||
final segaId = _segaIdController.text.trim();
|
||||
|
||||
if (segaId.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text("请输入或扫描二维码"), backgroundColor: Colors.orange),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
Uint8List? imageBytes;
|
||||
|
||||
setState(() {
|
||||
_isProcessing = true;
|
||||
_statusMessage = "正在准备图片...";
|
||||
});
|
||||
|
||||
try {
|
||||
// 策略:优先使用用户选择的截图作为载体。
|
||||
// 如果用户没选图,或者选图后删除了,我们可以下载默认背景图作为载体。
|
||||
if (_selectedImageFile != null) {
|
||||
imageBytes = await _selectedImageFile!.readAsBytes();
|
||||
} else {
|
||||
// 下载默认背景图作为隐写载体
|
||||
final httpClient = HttpClient();
|
||||
final request = await httpClient.getUrl(Uri.parse('https://union.godserver.cn/assets/jpeg/20180621142015_5FmGZ-wYXkyL4y.jpeg'));
|
||||
final response = await request.close();
|
||||
|
||||
// 辅助函数:将 HttpClientResponse 转为 Uint8List
|
||||
final bytes = <int>[];
|
||||
await for (final chunk in response) {
|
||||
bytes.addAll(chunk);
|
||||
}
|
||||
imageBytes = Uint8List.fromList(bytes);
|
||||
}
|
||||
|
||||
if (imageBytes == null || imageBytes.isEmpty) {
|
||||
throw Exception("图片数据无效");
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_statusMessage = "正在隐写并上传...";
|
||||
});
|
||||
|
||||
// 调用 Provider 上传
|
||||
final userProvider = context.read<UserProvider>();
|
||||
final result = await userProvider.uploadStegImage(imageBytes, segaId: segaId);
|
||||
|
||||
setState(() {
|
||||
_isProcessing = false;
|
||||
_statusMessage = "上传成功!";
|
||||
});
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text("上传成功: ${result['msg']}"), backgroundColor: Colors.green),
|
||||
);
|
||||
// 可选:清空表单
|
||||
// _segaIdController.clear();
|
||||
// _selectedImageFile = null;
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_isProcessing = false;
|
||||
_statusMessage = "上传失败: $e";
|
||||
});
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text("错误: $e"), backgroundColor: Colors.red),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
// 1. 背景图片
|
||||
Image.network(
|
||||
'https://union.godserver.cn/assets/jpeg/20180621142015_5FmGZ-wYXkyL4y.jpeg',
|
||||
fit: BoxFit.cover,
|
||||
loadingBuilder: (context, child, loadingProgress) {
|
||||
if (loadingProgress == null) return child;
|
||||
return Container(color: Colors.black);
|
||||
},
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return Container(color: Colors.grey[900]);
|
||||
},
|
||||
),
|
||||
|
||||
// 2. 深色遮罩
|
||||
Container(color: Colors.black.withOpacity(0.7)),
|
||||
|
||||
// 3. 主体内容
|
||||
SafeArea(
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// 标题
|
||||
const Text(
|
||||
"更新成绩 / 二维码",
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
|
||||
// 卡片容器
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: Colors.white.withOpacity(0.2)),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// 输入框
|
||||
TextField(
|
||||
controller: _segaIdController,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
decoration: InputDecoration(
|
||||
labelText: "Sega ID / 二维码内容",
|
||||
labelStyle: const TextStyle(color: Colors.white70),
|
||||
hintText: "手动输入或点击下方按钮识别",
|
||||
hintStyle: const TextStyle(color: Colors.white38),
|
||||
prefixIcon: const Icon(Icons.qr_code, color: Colors.white70),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(color: Colors.white38),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(color: Colors.white),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// 图片预览区域 (如果有)
|
||||
if (_selectedImageFile != null)
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Image.file(
|
||||
_selectedImageFile!,
|
||||
height: 150,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
|
||||
if (_selectedImageFile != null) const SizedBox(height: 15),
|
||||
|
||||
// 操作按钮组
|
||||
Row(
|
||||
children: [
|
||||
// 识别二维码按钮
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _isProcessing ? null : _pickImageAndScan,
|
||||
icon: const Icon(Icons.camera_alt),
|
||||
label: const Text("识别截图"),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blueAccent,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(width: 10),
|
||||
|
||||
// 上传按钮
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _isProcessing ? null : _handleUpload,
|
||||
icon: _isProcessing
|
||||
? const SizedBox(
|
||||
width: 16, height: 16,
|
||||
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white),
|
||||
)
|
||||
: const Icon(Icons.cloud_upload),
|
||||
label: Text(_isProcessing ? "处理中..." : "上传"),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.green,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// 状态提示
|
||||
if (_statusMessage != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: Text(
|
||||
_statusMessage!,
|
||||
style: TextStyle(
|
||||
color: _statusMessage!.contains("失败") || _statusMessage!.contains("错误")
|
||||
? Colors.redAccent
|
||||
: Colors.white70,
|
||||
fontSize: 14,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user