Uniapp Vue2环境H5模式实现二维码扫码功能 uniapp h5实现扫码

【投稿赢 iPhone 17】「我的第一个开源项目」故事征集:用代码换C位出道! 10w+人浏览 1.6k人参与

Uniapp Vue2环境H5模式实现二维码扫码功能 uniapp h5实现扫码

生产环境开启HTTPS

移动端扫码是需要 https环境下才可以进行扫码

全局安装 mkcert

npm install -g mkcert

生成证书文件

在项目根目录下打开终端,生成证书文件(cert.crt和 cert.key)

mkcert create-ca
mkcert create-cert

配置 vue.config.js​

在项目根目录创建或修改 vue.config.js文件,引入生成的证书

const path = require('path')
const fs = require('fs')

module.exports = {
  devServer: {
    https: {
      key: fs.readFileSync(path.join(__dirname, 'cert.key')), // 你的私钥文件路径
      cert: fs.readFileSync(path.join(__dirname, 'cert.crt')) // 你的证书文件路径
    },
    host: '0.0.0.0', // 可选,允许通过IP访问
    port: 8080, // 端口号
    open: true // 可选,启动后自动打开浏览器
  }
}

在这里插入图片描述

安装扫码库依赖

npm i html5-qrcode

独立扫码页面

扫码页面文件名:index.vue,路径:pages/html5QrCode/index
在这里插入图片描述

扫码页面代码

<!-- 独立的h5扫码页面 -->
<template>
  <view class="scan-container">
    <!-- 扫码区域 -->
    <view class="reader-box">
      <view class="reader" id="reader"></view>
    </view>
    
    <!-- 操作按钮 -->
    <view class="action-buttons">
      <button class="scan-btn" @tap="handleManualInput">手动输入</button>
    </view>
    
    <!-- 手动输入模态框 -->
    <uni-popup ref="inputPopup" type="dialog">
      <view class="input-modal">
        <text class="modal-title">手动输入编码</text>
        <input 
          v-model="manualCode" 
          class="code-input" 
          placeholder="请输入设备编码" 
          focus
        />
        <view class="modal-buttons">
          <button class="modal-btn cancel" @tap="closeModal">取消</button>
          <button class="modal-btn confirm" @tap="submitManualCode">确定</button>
        </view>
      </view>
    </uni-popup>
    
    <!-- 状态提示 -->
    <view class="status-tip" v-if="statusMessage">
      {{ statusMessage }}
    </view>
  </view>
</template>

<script>
import { Html5Qrcode } from 'html5-qrcode';

export default {
  data() {
    return {
      html5QrCode: null,
      cameraId: null,
      scanResult: null,
      statusMessage: '',
      manualCode: '',
      isScanning: false,
      hasScanned: false, // 新增:标记是否已扫码成功
      isStopping: false  // 新增:标记是否正在停止扫码
    };
  },
  onLoad() {
    this.initScan();
  },
  onUnload() {
    // 页面卸载时强制停止扫码
    this.forceStopScan();
  },
  onHide() {
    // 页面隐藏时停止扫码
    this.safeStopScan();
  },
  methods: {
    // 初始化扫码
    async initScan() {
      this.statusMessage = '正在初始化摄像头...';
      
      try {
        const devices = await Html5Qrcode.getCameras();
        if (!devices || devices.length === 0) {
          this.statusMessage = '未检测到可用摄像头';
          return;
        }
        
        let cameraIndex = devices.length > 1 ? 1 : 0;
        this.cameraId = devices[cameraIndex].id;
        this.startScan();
      } catch (err) {
        console.error('获取摄像头失败:', err);
        this.handleCameraError(err);
      }
    },
    
    // 开始扫码
    startScan() {
      if (!this.cameraId || this.isScanning || this.hasScanned) return;
      
      this.html5QrCode = new Html5Qrcode('reader');
      this.isScanning = true;
      this.isStopping = false;
      
      const scanConfig = {
        fps: 20,
        qrbox: 300,
        video: { facingMode: 'environment' }
      };
      
      this.html5QrCode.start(
        this.cameraId,
        scanConfig,
        this.handleScanSuccess,
        this.handleScanError
      ).catch(err => {
        console.error('扫码启动失败:', err);
        this.handleCameraError(err);
      });
      
      this.statusMessage = '请对准二维码';
    },
    
    // 处理扫码成功 - 关键修复
    async handleScanSuccess(decodedText) {
      // 防止重复处理
      if (this.hasScanned) return;
      
      this.scanResult = decodedText;
      this.hasScanned = true; // 标记已扫码成功
      console.log('扫码成功:', decodedText);
      
      // 安全停止扫码
      await this.safeStopScan();
      
      // 返回结果到上一个页面
      const pages = getCurrentPages();
      const prevPage = pages[pages.length - 2];
      
      if (prevPage && prevPage.$vm) {
        prevPage.$vm.$emit('scan-result', this.scanResult);
      }
      
      // 确保扫码完全停止后再返回
      setTimeout(() => {
        uni.navigateBack();
      }, 100);
    },
    
    // 处理扫码错误
    handleScanError(errorMessage) {
      console.log('扫码错误:', errorMessage);
    },
    
    // 处理摄像头错误
    handleCameraError(err) {
      let message = "扫码功能不可用";
      
      if (err.name === "NotAllowedError") {
        message = "请允许浏览器访问摄像头权限";
      } else if (err.name === "NotFoundError") {
        message = "未找到可用摄像头";
      } else if (err.name === "NotSupportedError") {
        message = "当前环境不支持摄像头访问";
      } else if (err.name === "NotReadableError") {
        message = "摄像头被占用,请关闭其他应用";
      }
      
      this.statusMessage = message;
      this.isScanning = false;
    },
    
    // 安全停止扫码
    safeStopScan() {
      return new Promise((resolve) => {
        if (!this.html5QrCode || !this.isScanning || this.isStopping) {
          resolve();
          return;
        }
        
        this.isStopping = true;
        
        this.html5QrCode.stop().then(() => {
          console.log("扫码已安全停止");
          this.isScanning = false;
          this.isStopping = false;
          resolve();
        }).catch(err => {
          console.log("停止扫码失败:", err);
          this.isScanning = false;
          this.isStopping = false;
          resolve();
        });
      });
    },
    
    // 强制停止扫码
    forceStopScan() {
      if (this.html5QrCode) {
        try {
          // 直接停止,不等待结果
          this.html5QrCode.stop().catch(() => {});
          this.isScanning = false;
        } catch (err) {
          console.log('强制停止扫码异常:', err);
        }
      }
    },
    
    // 手动输入
    handleManualInput() {
      this.safeStopScan().then(() => {
        this.$refs.inputPopup.open();
      });
    },
    
    // 提交手动输入
    submitManualCode() {
      if (this.manualCode && !this.hasScanned) {
        this.handleScanSuccess(this.manualCode);
      }
      this.closeModal();
    },
    
    // 关闭模态框
    closeModal() {
      this.$refs.inputPopup.close();
      this.manualCode = '';
      if (!this.hasScanned) {
        this.startScan();
      }
    }
  }
};
</script>

<style lang="scss">
.scan-container {
  display: flex;
  flex-direction: column;
  width: 100%;
  background-color: #000;
  
  .reader-box {
    flex: 1;
    position: relative;
    overflow: hidden;
    background-color: rgba(0, 0, 0, 0.7);
    
    .reader {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
    }
    
    .reader video{
      width: 100% !important;
      height: 100% !important;
      object-fit: cover !important;
    }
  }
  
  .action-buttons {
    display: flex;
    justify-content: space-around;
    padding: 20rpx;
    background-color: rgba(0, 0, 0, 0.8);
    
    .scan-btn {
      flex: 1;
      margin: 0 10rpx;
      background-color: #5f77fd;
      color: #fff;
      border-radius: 50rpx;
      font-size: 28rpx;
    }
  }
  
  .input-modal {
    padding: 40rpx;
    background-color: #fff;
    border-radius: 16rpx;
    width: 80vw;
    
    .modal-title {
      display: block;
      text-align: center;
      font-size: 36rpx;
      font-weight: bold;
      margin-bottom: 40rpx;
      color: #333;
    }
    
    .code-input {
      height: 100rpx;
      border: 2rpx solid #eee;
      border-radius: 10rpx;
      padding: 0 20rpx;
      margin-bottom: 40rpx;
      font-size: 32rpx;
    }
    
    .modal-buttons {
      display: flex;
      justify-content: space-between;
      
      .modal-btn {
        flex: 1;
        margin: 0 10rpx;
        height: 80rpx;
        line-height: 80rpx;
        border-radius: 10rpx;
        font-size: 32rpx;
        
        &.cancel {
          background-color: #f8f8f8;
          color: #666;
        }
        
        &.confirm {
          background-color: #5f77fd;
          color: #fff;
        }
      }
    }
  }
  
  .status-tip {
    position: absolute;
    bottom: 150rpx;
    left: 0;
    right: 0;
    text-align: center;
    color: #fff;
    font-size: 32rpx;
    padding: 20rpx;
    background-color: rgba(0, 0, 0, 0.5);
  }
}
</style>

配置扫码页面

pages.json配置当中增加这个页面

{
      "path": "pages/html5QrCode/index",
      "style": {
        "enablePullDownRefresh": true,
        "titleNView": {
          "titleText": "二维码扫描",
          "titleColor": "#fff",
          "titleSize": "36rpx",
          "backgroundColor": "#5f77fd"
        },
        "backgroundColor": "#F9F9F9",
        "scrollIndicator": "none"
      }
    }

页面调用扫码功能

<template>
  <view>扫码结果:{{scanVal}}</view>
</template>
<script>
export default {
  data() {
  	return {
  		scanVal: ''
  	}
  },
  // 在页面加载时添加事件监听
  onLoad() {
   // 监听扫码结果事件
   this.$on('scan-result', this.handleScanResult);
  },
  methods: {
    // h5打开扫码页面
    h5ScanCode() {
      uni.navigateTo({
        url: '/pages/scan-code/scan-code'
      });
    },
    // 处理扫码结果
    async handleScanResult(result) {
       // 先隐藏所有的弹出提示,避免下面扫码结果处理的提示被覆盖
       uni.hideLoading();
       
      // 增加延迟,用于解决扫码返回多个逻辑一起处理导致页面异常
      await new Promise(resolve => setTimeout(resolve, 500));
      await new Promise(resolve => setTimeout(resolve, 100));
      console.log('收到扫码结果:', result);
      this.scanVal = result;
      // 处理扫码结果...
    }
  }
}
</script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值