• 除了 H5 之外的其他环境都可以用 uni.scanCode
  • 需要 https 环境

安装:npm install html5-qrcode --save

(样式很随意、很丑,但是能用。)

<template>
  <view class="scan-container">
    <!-- 扫码区域 -->
    <view id="qr-reader" style="width: 100%;"></view>
	
	<!-- 扫描框UI -->
	<view class="scan-mask" v-if="isScanning">
	  <view class="scan-box">
	    <view class="scan-line" :style="{top: linePosition+'px'}"></view>
	  </view>
	</view>
    
    <!-- 结果展示 -->
    <!-- <view v-if="scanResult" class="result-box">
      <text>扫描结果: {{ scanResult }}</text>
    </view> -->
    
    <!-- 控制按钮 -->
    <view class="btn-group">
      <button @click="startScan" :disabled="isScanning">开始扫描</button>
      <button @click="stopScan" :disabled="!isScanning">停止扫描</button>
    </view>
    
    <u-toast ref="uToast"></u-toast>
  </view>
</template>

<script>
// #ifdef H5
import { Html5Qrcode } from 'html5-qrcode';
// #endif

export default {
  data() {
    return {
      html5QrCode: null,
      isScanning: false,
      scanResult: null,
      cameraId: null
    };
  },
  mounted() {
    // #ifdef H5
    this.initScanner();
    // #endif
  },
  beforeDestroy() {
    this.stopScan();
  },
  methods: {
    // #ifdef H5
	async initScanner() {
	  try {
	    // 获取摄像头设备列表
	    const devices = await Html5Qrcode.getCameras();
	    if (devices && devices.length > 0) {
	      // 优先选择后置摄像头
	      const backCamera = devices.find(device => 
	        device.label.toLowerCase().includes('back') || device.facingMode === 'environment'
	      );
	      
	      // 如果没有找到后置摄像头,则选择前置摄像头
	      const frontCamera = devices.find(device => 
	        device.label.toLowerCase().includes('front') || device.facingMode === 'user'
	      );
	      
	      // 根据设备选择相应的摄像头, devices[0] 是第一个摄像头/前置摄像。
	      this.cameraId = backCamera ? backCamera.id : frontCamera ? frontCamera.id : devices[1].id;
	    } else {
	      throw new Error('未检测到可用摄像头');
	    }
	  } catch (err) {
	    console.error('摄像头初始化失败:', err);
	    this.showToast(`摄像头错误: ${err.message}`);
	  }
	},
    
    async startScan() {
      if (this.isScanning) return;
      
      try {
        this.html5QrCode = new Html5Qrcode("qr-reader");
        
        const config = {
		  facingMode: "environment", // 明确要求后置摄像头
          fps: 20, // 提高帧率,适当提升扫描速度
          // qrbox: { width: 250, height: 250 }, // 扫码框大小
          aspectRatio: 1.0 // 宽高比
        };
        
        await this.html5QrCode.start(
          this.cameraId,
          config,
          this.onScanSuccess,
          this.onScanError
        );
        
        this.isScanning = true;
        this.scanResult = null;
      } catch (err) {
        console.error('扫码启动失败:', err);
        this.showToast(`扫码启动失败: ${err.message}`);
      }
    },
    
    onScanSuccess(decodedText, decodedResult) {
      this.scanResult = decodedText;
      // this.showToast(`扫描成功: ${decodedText}`);
      this.stopScan();
      
      // 处理扫描结果
      this.handleScanResult(decodedText);
    },
    
    onScanError(errorMessage) {
      // 忽略部分非致命错误
      if (!errorMessage.includes('NotFoundException')) {
        console.warn('扫码错误:', errorMessage);
      }
    },
    
    stopScan() {
      if (this.html5QrCode && this.isScanning) {
        this.html5QrCode.stop()
          .then(() => {
            this.isScanning = false;
            this.html5QrCode.clear();
          })
          .catch(err => {
            console.error('扫码停止失败:', err);
          });
      }
    },
    // #endif
    
    handleScanResult(result) {
      // 在这里处理扫描结果
      uni.showModal({
        title: '扫描结果',
        content: result,
        showCancel: false,
		success: (res) => {
		  console.log('模态框回调:', res);
		  if (res.confirm) {
			  uni.navigateBack();
		  }else{
				  
		  }
		}
      });
    },
    
    showToast(message) {
      this.$refs.uToast.show({
        message: message,
        type: 'info',
        duration: 2000
      });
    }
  }
};
</script>

<style>
.scan-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 20px;
}

#qr-reader {
  margin: 20px 0;
  border: 2px solid #eee;
  border-radius: 8px;
  overflow: hidden;
}

.result-box {
  margin: 20px;
  padding: 15px;
  background-color: #f5f5f5;
  border-radius: 8px;
  width: 80%;
  text-align: center;
}

.btn-group {
  display: flex;
  justify-content: center;
  gap: 15px;
  margin-top: 20px;
}

button {
  padding: 10px 20px;
  background-color: #007aff;
  color: white;
  border: none;
  border-radius: 5px;
  font-size: 16px;
}

button:disabled {
  background-color: #cccccc;
}

.scan-mask {
  position: absolute;
  top: 20px;
  left: 50%;
  transform: translateX(-50%);
  width: 100%;
  max-width: 500px;
  height: calc(100% - 160px);
  display: flex;
  justify-content: center;
  align-items: center;
  pointer-events: none;
}

.scan-box {
  width: 250px;
  height: 250px;
  border: 2px solid rgba(0, 255, 0, 0.5);
  position: relative;
  overflow: hidden;
}

.scan-line {
  position: absolute;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(to right, transparent, #00ff00, transparent);
  animation: scanLine 3s linear infinite;
}

@keyframes scanLine {
  0% { top: 0; }
  100% { top: 100%; }
}

</style>

更多推荐