ver1.00.00

bindQR
fix
This commit is contained in:
spasolreisa
2026-04-19 23:31:52 +08:00
parent c5c3f7c8f5
commit b985cd1f9e
4 changed files with 166 additions and 200 deletions

View File

@@ -7,29 +7,29 @@ import 'package:provider/provider.dart';
import '../../providers/user_provider.dart';
class UpdateScorePage extends StatefulWidget {
const UpdateScorePage({super.key});
class BindAccountPage extends StatefulWidget {
const BindAccountPage({super.key});
@override
State<UpdateScorePage> createState() => _UpdateScorePageState();
State<BindAccountPage> createState() => _BindAccountPageState();
}
class _UpdateScorePageState extends State<UpdateScorePage> {
final TextEditingController _segaIdController = TextEditingController();
class _BindAccountPageState extends State<BindAccountPage> {
final TextEditingController _qrContentController = TextEditingController();
final ImagePicker _picker = ImagePicker();
bool _isProcessing = false;
String? _statusMessage;
File? _selectedImageFile; // 用于预览
File? _selectedImageFile; // 用户选择的原始二维码截图
// 条码扫描器 (支持 QR Code, Data Matrix, Aztec 等)
// 条码扫描器
final BarcodeScanner _barcodeScanner = BarcodeScanner(
formats: [BarcodeFormat.qrCode, BarcodeFormat.dataMatrix, BarcodeFormat.aztec],
formats: [BarcodeFormat.qrCode],
);
@override
void dispose() {
_segaIdController.dispose();
_qrContentController.dispose();
_barcodeScanner.close();
super.dispose();
}
@@ -46,7 +46,6 @@ class _UpdateScorePageState extends State<UpdateScorePage> {
_selectedImageFile = File(pickedFile.path);
});
// 执行二维码识别
final inputImage = InputImage.fromFilePath(pickedFile.path);
final List<Barcode> barcodes = await _barcodeScanner.processImage(inputImage);
@@ -54,23 +53,15 @@ class _UpdateScorePageState extends State<UpdateScorePage> {
_isProcessing = false;
});
if (barcodes.isNotEmpty) {
// 取第一个识别到的二维码内容
final String code = barcodes.first.rawValue ?? "";
if (code.isNotEmpty) {
setState(() {
_segaIdController.text = code;
_statusMessage = "识别成功: $code";
});
} else {
setState(() {
_statusMessage = "二维码内容为空";
});
}
if (barcodes.isNotEmpty && barcodes.first.rawValue != null) {
final String code = barcodes.first.rawValue!;
setState(() {
_qrContentController.text = code;
_statusMessage = "识别成功";
});
} else {
setState(() {
_statusMessage = "在图中发现二维码,请手动输入";
_statusMessage = "识别到二维码,请手动输入";
});
}
@@ -82,13 +73,13 @@ class _UpdateScorePageState extends State<UpdateScorePage> {
}
}
// 2. 执行隐写上传
Future<void> _handleUpload() async {
final segaId = _segaIdController.text.trim();
// 2. 执行隐写上传 (触发后端绑定/登录逻辑)
Future<void> _handleBind() async {
final qrContent = _qrContentController.text.trim();
if (segaId.isEmpty) {
if (qrContent.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("请输入或扫描二维码"), backgroundColor: Colors.orange),
const SnackBar(content: Text("请输入或扫描二维码内容"), backgroundColor: Colors.orange),
);
return;
}
@@ -97,58 +88,54 @@ class _UpdateScorePageState extends State<UpdateScorePage> {
setState(() {
_isProcessing = true;
_statusMessage = "正在准备图片...";
_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);
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();
final bytes = <int>[];
await for (final chunk in response) {
bytes.addAll(chunk);
}
imageBytes = Uint8List.fromList(bytes);
if (imageBytes == null || imageBytes.isEmpty) {
throw Exception("图片数据无效");
}
if (imageBytes == null || imageBytes.isEmpty) throw Exception("图片数据无效");
setState(() {
_statusMessage = "正在隐写并上传...";
_statusMessage = "正在提交绑定请求...";
});
// 调用 Provider 上传
// 调用 Provider
// 注意:这里 segaId 参数其实传的是 QR Code 的内容
final userProvider = context.read<UserProvider>();
final result = await userProvider.uploadStegImage(imageBytes, segaId: segaId);
final result = await userProvider.uploadStegImage(imageBytes, segaId: qrContent);
setState(() {
_isProcessing = false;
_statusMessage = "上传成功!";
});
// 处理后端返回
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("上传成功: ${result['msg']}"), backgroundColor: Colors.green),
);
// 可选:清空表单
// _segaIdController.clear();
// _selectedImageFile = null;
if (result['code'] == 200) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("${result['msg']}"), backgroundColor: Colors.green, duration: const Duration(seconds: 5)),
);
// 绑定成功后,通常建议刷新用户信息
await userProvider.initUser();
} else {
// 后端返回 500 或其他错误
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("${result['msg']}"), backgroundColor: Colors.red, duration: const Duration(seconds: 5)),
);
}
}
} catch (e) {
setState(() {
_isProcessing = false;
_statusMessage = "上传失败: $e";
_statusMessage = "网络或处理错误: $e";
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
@@ -161,10 +148,15 @@ class _UpdateScorePageState extends State<UpdateScorePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("绑定 / 刷新 Sega 账号"),
backgroundColor: Colors.black.withOpacity(0.5),
elevation: 0,
),
body: Stack(
fit: StackFit.expand,
children: [
// 1. 背景图
// 背景图
Image.network(
'https://union.godserver.cn/assets/jpeg/20180621142015_5FmGZ-wYXkyL4y.jpeg',
fit: BoxFit.cover,
@@ -172,15 +164,13 @@ class _UpdateScorePageState extends State<UpdateScorePage> {
if (loadingProgress == null) return child;
return Container(color: Colors.black);
},
errorBuilder: (context, error, stackTrace) {
return Container(color: Colors.grey[900]);
},
errorBuilder: (context, error, stackTrace) => Container(color: Colors.grey[900]),
),
// 2. 深色遮罩
Container(color: Colors.black.withOpacity(0.7)),
// 遮罩
Container(color: Colors.black.withOpacity(0.75)),
// 3. 主体内容
// 内容
SafeArea(
child: Center(
child: SingleChildScrollView(
@@ -188,37 +178,39 @@ class _UpdateScorePageState extends State<UpdateScorePage> {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 标题
const Icon(Icons.qr_code_scanner, size: 60, color: Colors.white70),
const SizedBox(height: 16),
const Text(
"更新成绩 / 二维码",
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
"请提供 Sega 登录二维码",
style: TextStyle(fontSize: 18, color: Colors.white, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
const Text(
"支持识别截图或手动输入二维码内容\n用于绑定账号或刷新登录状态",
style: TextStyle(fontSize: 14, color: Colors.white60),
textAlign: TextAlign.center,
),
const SizedBox(height: 30),
// 卡片容器
// 输入卡片
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.15),
color: Colors.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.white.withOpacity(0.2)),
),
child: Column(
children: [
// 输入框
TextField(
controller: _segaIdController,
controller: _qrContentController,
style: const TextStyle(color: Colors.white),
maxLines: 10,
decoration: InputDecoration(
labelText: "Sega ID / 二维码内容",
labelText: "二维码内容 (QR Data)",
labelStyle: const TextStyle(color: Colors.white70),
hintText: "手动输入或点击下方按钮识别",
hintText: "例如: SGWCMAID...",
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),
@@ -232,60 +224,42 @@ class _UpdateScorePageState extends State<UpdateScorePage> {
const SizedBox(height: 20),
// 图片预览区域 (如果有)
// 预览
if (_selectedImageFile != null)
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.file(
_selectedImageFile!,
height: 150,
fit: BoxFit.contain,
),
child: Image.file(_selectedImageFile!, height: 120, 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),
icon: const Icon(Icons.image_search),
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),
// 上传按钮
const SizedBox(width: 12),
Expanded(
flex: 1,
child: ElevatedButton.icon(
onPressed: _isProcessing ? null : _handleUpload,
onPressed: _isProcessing ? null : _handleBind,
icon: _isProcessing
? const SizedBox(
width: 16, height: 16,
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white),
)
: const Icon(Icons.cloud_upload),
label: Text(_isProcessing ? "处理中..." : "上传"),
? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white))
: const Icon(Icons.login),
label: Text(_isProcessing ? "处理中" : "绑定/刷新"),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
),
@@ -295,17 +269,14 @@ class _UpdateScorePageState extends State<UpdateScorePage> {
),
),
// 状态提示
// 状态消息
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,
color: _statusMessage!.contains("失败") || _statusMessage!.contains("错误") ? Colors.redAccent : Colors.white70,
),
textAlign: TextAlign.center,
),