You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter QR码扫描器优化:如何仅识别特定产品QR码?

实现仅扫描特定产品QR码的功能

要让你的Flutter二维码扫描器只识别特定产品的QR码,核心是在条码检测完成后加入验证逻辑,只处理符合预设规则的QR码,拒绝无关内容。以下是具体实现方案:

步骤1:定义特定产品的QR码规则

根据你的产品类别,为每个类别设定识别规则(比如前缀、关键词或正则表达式)。示例中我们给三类产品分别设定前缀规则:

  • 显卡(Graphics Card):QR码内容以 GPU- 开头
  • 主板(Motherboard):以 MB- 开头
  • 处理器(Processor):以 CPU- 开头

你可以根据实际需求调整规则,比如使用正则匹配复杂格式,或者包含特定关键词。

步骤2:修改条码检测逻辑

在onBarcodeDetected方法中加入验证逻辑,只有符合规则的QR码才进入保存流程;不符合则提示用户并重置扫描状态。

修改后的完整代码

以下是更新后的QrScanner代码,重点标记了新增/修改的部分:

import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:provider/provider.dart';
import 'package:audioplayers/audioplayers.dart';
import 'serial_number_model.dart';
import 'serial_number_history.dart';
import 'inventory.dart';

const backgroundColor = Color.fromARGB(248, 248, 245, 245);

// 新增:定义特定产品的QR码验证规则
Map<String, bool Function(String)> productQrRules = {
  'Graphics Card': (code) => code.startsWith('GPU-'),
  'Motherboard': (code) => code.startsWith('MB-'),
  'Processor': (code) => code.startsWith('CPU-'),
};

// 新增:判断QR码是否属于任何目标产品
bool isValidProductQr(String code) {
  return productQrRules.values.any((rule) => rule(code));
}

class QrScanner extends StatefulWidget {
  const QrScanner({super.key});

  @override
  State<QrScanner> createState() => _QrScannerState();
}

class _QrScannerState extends State<QrScanner> {
  bool hasScanned = false;
  bool isFlashActive = false;
  CameraFacing cameraDirection = CameraFacing.back;
  late MobileScannerController scannerController;
  late AudioPlayer audioPlayer;

  @override
  void initState() {
    super.initState();
    scannerController = MobileScannerController();
    audioPlayer = AudioPlayer();
  }

  Future<void> playScanSound() async {
    await audioPlayer.play(AssetSource('scan.mp3'));
  }

  // 修改:加入QR码验证逻辑
  void onBarcodeDetected(Barcode barcode) {
    if (!hasScanned) {
      final code = barcode.rawValue ?? 'Unknown Code';
      setState(() {
        hasScanned = true;
      });

      // 验证QR码是否符合规则
      if (isValidProductQr(code)) {
        playScanSound();
        showSaveDialog(code);
      } else {
        // 提示无效QR码并重置扫描状态
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('无效的产品QR码,请扫描目标产品')),
        );
        resetScanState();
      }
    }
  }

  void showSaveDialog(String code) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('保存序列号!'),
        content: const Text('请选择保存类别:'),
        actions: [
          ...buildCategoryButtons(code),
          TextButton(
            child: const Text('取消'),
            onPressed: () {
              Navigator.pop(context);
              resetScanState();
            },
          ),
        ],
      ),
    );
  }

  List<Widget> buildCategoryButtons(String code) {
    final categories = ['Graphics Card', 'Motherboard', 'Processor'];
    return categories.map((category) {
      // 可选:根据QR码自动匹配对应类别(比如前缀对应类别)
      bool isEnabled = productQrRules[category]!(code);
      return TextButton(
        child: Text(category),
        onPressed: isEnabled
            ? () {
                saveSerialNumber(category, code);
                Navigator.pop(context);
              }
            : null, // 不符合类别的按钮置灰不可点击
      );
    }).toList();
  }

  Future<void> saveSerialNumber(String category, String code) async {
    await Provider.of<SerialNumberModel>(context, listen: false)
        .addSerialNumber(category, code);
    if (!mounted) return;

    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => SerialNumberHistoryScreen(
          category: category,
          initialSerialNumbers: Provider.of<SerialNumberModel>(context)
              .getSerialNumbers(category)
              .map((map) => map['serialNumber']!)
              .toList(),
          serialNumbers: const [],
        ),
      ),
    );

    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('已保存到 $category!')),
    );
    resetScanState();
  }

  void resetScanState() {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      setState(() {
        hasScanned = false;
      });
    });
  }

  Future<void> toggleFlash() async {
    setState(() {
      isFlashActive = !isFlashActive;
    });
    scannerController.toggleTorch();
  }

  void switchCamera() {
    setState(() {
      cameraDirection = cameraDirection == CameraFacing.back
          ? CameraFacing.front
          : CameraFacing.back;
      scannerController.switchCamera();

      String cameraPosition =
          cameraDirection == CameraFacing.front ? '前置' : '后置';
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('已切换到$cameraPosition摄像头')),
      );
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: backgroundColor,
      appBar: AppBar(
        actions: [
          IconButton(
            onPressed: toggleFlash,
            icon: Icon(isFlashActive ? Icons.flash_off : Icons.flash_on,
                color: Colors.grey),
          ),
          IconButton(
            onPressed: switchCamera,
            icon: const Icon(Icons.camera, color: Colors.grey),
          ),
        ],
        iconTheme: const IconThemeData(color: Color.fromARGB(221, 131, 97, 97)),
        centerTitle: true,
        title: const Text("QR扫描器",
            style: TextStyle(
                color: Color.fromARGB(221, 0, 0, 0),
                fontWeight: FontWeight.bold)),
        leading: Builder(builder: (context) {
          return IconButton(
            icon: const Icon(Icons.menu),
            onPressed: () =>
                Scaffold.of(context).openDrawer(),
          );
        }),
      ),
      body: Container(
        width: double.infinity,
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            const Expanded(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text("请将设备对准目标产品的QR码",
                      style: TextStyle(
                          color: Colors.black87,
                          fontSize: 18,
                          fontWeight: FontWeight.bold)),
                  SizedBox(height: 10),
                  Text("扫描将自动进行"),
                ],
              ),
            ),
            Expanded(
              flex: 4,
              child: Center(
                child: SizedBox(
                  width: double.infinity,
                  height: 400,
                  child: Stack(
                    alignment: Alignment.center,
                    children: [
                      Container(
                        decoration: BoxDecoration(
                            borderRadius: BorderRadius.circular(12),
                            border: Border.all(color: Colors.black, width: 2)),
                      ),
                      SizedBox(
                        width: double.infinity,
                        height: double.infinity,
                        child: MobileScanner(
                          controller: scannerController,
                          onDetect: (BarcodeCapture barcodeCapture) {
                            onBarcodeDetected(barcodeCapture.barcodes.first);
                          },
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ),
            const Expanded(
              child: Align(
                  alignment: Alignment.center,
                  child: Text("TechShack",
                      style: TextStyle(
                          color: Color.fromARGB(221, 247, 228, 228),
                          fontSize: 14))),
            ),
          ],
        ),
      ),
      drawer: Drawer(
        child: ListView(
          padding: EdgeInsets.zero,
          children: [
            DrawerHeader(
              decoration: const BoxDecoration(
                color: Color.fromARGB(
                    255, 73, 167, 255),
              ),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  const Text(
                    'TechShack',
                    style: TextStyle(
                      color: Colors.black,
                      fontSize: 24,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  Image.asset(
                    'assets/logo.png',
                    height: 120,
                    width: 120,
                    fit: BoxFit.cover,
                  ),
                ],
              ),
            ),
            buildDrawerItem('Graphics Card'),
            buildDrawerItem('Motherboard'),
            buildDrawerItem('Processor'),
            ListTile(
              title: const Text('库存'),
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => const Inventory(),
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }

  Widget buildDrawerItem(String category) {
    return ListTile(
      title: Text(category),
      onTap: () {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => Consumer<SerialNumberModel>(
              builder: (context, serialNumberModel, child) {
                return SerialNumberHistoryScreen(
                  category: category,
                  initialSerialNumbers: serialNumberModel
                      .getSerialNumbers(category)
                      .map((map) => map['serialNumber']!)
                      .toList(),
                  serialNumbers: const [],
                );
              },
            ),
          ),
        );
      },
    );
  }
}

额外优化说明

  • 自动匹配类别:在buildCategoryButtons中,我们给不符合类别的按钮设置了onPressed: null,这样用户只能选择对应类别的保存选项,避免误操作。
  • 规则可扩展性:productQrRules是一个Map结构,你可以轻松添加更多产品类别的规则,比如'RAM': (code) => code.startsWith('RAM-')。
  • 自定义规则:如果你的QR码格式更复杂,可以使用正则表达式,比如:
    'Graphics Card': (code) => RegExp(r'^GPU-\d{8}$').hasMatch(code),
    
    这个规则表示显卡的QR码必须是GPU-开头加8位数字。

内容的提问来源于stack exchange,提问作者camelk12334

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 03:17:02