更新记录

1.0.1(2026-03-30)

文档新增app持续扫码指引

1.0.0(2026-03-30)

1.使用原生组件camera封装,完美支持微信小程序


平台兼容性

uni-app(4.56)

Vue2 Vue2插件版本 Vue3 Vue3插件版本 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
1.0.0 1.0.0 - - - - - - -
微信小程序 微信小程序插件版本 支付宝小程序 支付宝小程序插件版本 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
1.0.0 1.0.0 - - - - - - - - - -

yx-customScan

App 端nvue)需要 持续扫码、不关闭相机 等场景时,请使用 DCloud 插件 yx-curstomAppScan,详见:App 端持续扫码插件,用于扫码不关闭场景 - yx-curstomAppScan
本文档 yx-customScan 仅面向 微信小程序(原生 <camera>),与上述 App 插件互为独立方案。

微信小程序 持续扫码(原生 <camera>mode 默认 scanCode)封装。实现为 Vue Options API,可在 uni-app Vue 2 / Vue 3 中使用。

说明

  • 平台:仅 #ifdef MP-WEIXIN 为真实扫码;其它端编译通过,页面内为「不支持」占位提示。
  • 内置:左上角返回条、扫描线动效。不内置业务成功/失败 Toast,请监听 success / error 自行处理。
  • 亮屏:相机 initdone 后插件内会调用 uni.setKeepScreenOn 保持屏幕常亮(实现见内层 applyScanKeepScreenOn,含当前工程中的参数写法)。
  • #footer / #overlay:须放在 <camera> 内;微信小程序里 自定义层请使用 cover-view / cover-image(及允许的文本节点),普通 view 无法正常盖在相机预览上或无法点击。
  • 根组件 inheritAttrs: false,未声明为 props 的 合法 <camera> 属性与部分事件 会通过 v-bind="attrsWithVue2Listeners" 透传到内层(详见下节)。根组件已声明 emits: ['back','success','error'],故 @back / @success / @error 不会进入 $attrs

引入方式

页面内直接 import(与当前业务页一致):

import YxUniCustomScan from '@/uni_modules/yx-customScan/components/yx-customScan/yx-customScan.vue'
<template>
  <view class="page" :style="{ height: '100vh' }">
    <YxUniCustomScan
      ref="scanRef"
      :flash-on="flashOn"
      @back="onBack"
      @success="onScanSuccess"
      @error="onScanError"
    >
      <template #footer>
        <!-- 须 cover-view / cover-image -->
      </template>
    </YxUniCustomScan>
  </view>
</template>

也可在 pages.json / pages.config.ts 中配置 easycom(规则按项目约定调整):

custom: {
  '^yx-custom-scan$':
    '@/uni_modules/yx-customScan/components/yx-customScan/yx-customScan.vue',
},

Props(根组件)

属性 类型 默认值 说明
flashOn Boolean false 是否手电筒;与 $attrs.flash 同时存在时 flash 为准;变更会参与内层 camera 的重建 key
scanLineImage String '' 扫描线图片 URL;空则用内置资源路径(见内层 static/scanline.png
scanLineDurationSec Number 3 扫描线动画周期(秒)
successAudioSrc String '' 成功音效 URL;空则用内置 audio/scan-success.mp3
playSuccessSound Boolean false true 时,每次解码成功后自动轻震 + 播放成功音;为 false 时可在 success 载荷里 playSuccessFeedback() 自选时机调用
backIconSrc String '' 返回按钮图;空则用内置 static/back.png

另:可向根组件传递 uni-app / 微信小程序 <camera> 文档中的属性(如 iddevice-positionresolutionframe-sizeclassstyleflashmode 等),由内层读 $attrs 合并到原生节点(原生 <camera> 上未在源码中显式列出的属性不会自动落表,需改内层 wxScanCamera.vue)。

事件

事件 说明
back 点击左上角返回
success 解码成功;回调参数为对象,见下表
error 相机错误

success 载荷

字段 说明
value 字符串,扫码结果
oldvalue 原始 detail.result
eventData 小程序 scancode 原始事件
id 本地生成的时间戳 id
playSuccessFeedback () => void,与根组件 ref.playSuccessFeedback() 相同:短震 + 按配置播放成功音;不受 playSuccessSound 限制,便于在业务通过后再播

插槽

插槽 说明
overlay 扫描线与返回条之下、footer 之上;须 cover-view / cover-image;注意与返回条 z-index
footer 相机内部底部区域;须 cover-*

底部栏示例:WxScanFooter(可复制到业务工程)

以下示例来自业务仓库 src/pages-qr/qrcode/components/wxScanFooter.vue不在插件包内,使用插件时复制到你的项目(例如 components/wx-scan-footer/wx-scan-footer.vue),并把 iconFlashOff / iconFlashOn 的 import 改成你自己的手电筒关/开图路径。

#footer 内必须使用 cover-view / cover-image
微信小程序原生 <camera> 层级最高,普通 view / button 盖不住预览也无法点击;底部条请在 camera 内用 *`cover-** 编写。底部用 **position: absolute贴底** 即可,**不要**用position: fixed` 甩在相机外凑合。

若使用 Vue 2,请将下方 <script setup> 改为 Options APIprops / emits),模板与样式可保持不变。

<template>
  <!-- 必须放在 <camera> 内;原生相机层级上仅 cover-view / cover-image 可点 -->
  <cover-view class="footer" :style="{ paddingBottom: `${safeBottom}px` }">
    <cover-view class="footer-cell footer-cell--left">
      <!-- 角标在条内顶部居中(纵向排列),不溢出父级,避免真机被裁切 -->
      <cover-view class="label-wrap">
        <cover-view class="badge">
          <cover-view class="badge-text">
            {{ badgeText }}
          </cover-view>
        </cover-view>
        <cover-view class="label">
          已扫描
        </cover-view>
      </cover-view>
    </cover-view>
    <cover-view class="footer-cell footer-cell--mid">
      <cover-view class="flash-btn" @click="Flash">
        <cover-image
          class="flash-icon"
          :src="flashOn ? iconFlashOn : iconFlashOff"
        />
      </cover-view>
    </cover-view>
    <cover-view class="footer-cell footer-cell--right">
      <cover-view class="finish-btn" :class="{ 'finish-btn--loading': loading }" @click="onFinishClick">
        <cover-view class="finish-text">
          {{ loading ? '提交中...' : finishText }}
        </cover-view>
      </cover-view>
    </cover-view>
  </cover-view>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import iconFlashOff from '../../static/icons/closedeng.png'
import iconFlashOn from '../../static/icons/open.png'

defineOptions({ name: 'WxScanFooter' })

const props = withDefaults(defineProps<{
  count: number
  safeBottom: number
  flashOn: boolean
  loading: boolean
  /** 与 nvue ScanFooter 一致:有持久化待处理时可传「待处理(n)」 */
  finishText?: string
}>(), {
  finishText: '扫描完成',
})

const emit = defineEmits<{
  (e: 'finish'): void
  (e: 'toggle-flash'): void
}>()

const badgeText = computed(() => (props.count > 99 ? '99+' : String(props.count)))

function Flash() {
  emit('toggle-flash')
}

function onFinishClick() {
  if (props.loading)
    return
  emit('finish')
}
</script>

<style scoped>
/* 在 camera 内用 absolute 贴底;勿用 fixed(相对原生层易错位且不响应点击) */
.footer {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 20;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  min-height: 112rpx;
  padding-top: 16rpx;
  padding-left: 16rpx;
  padding-right: 16rpx;
  background-color: #2c2c2c;
  overflow: visible;
}

.footer-cell {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  overflow: visible;
}

/* 左:按需占位并限宽,给右侧「扫描完成 / 待处理(n)」留足横向空间 */
.footer-cell--left {
  flex: 0 1 auto;
  min-width: 88px;
  max-width: 30%;
  overflow: visible;
}

/* 中:固定手电宽度,不参与均分 */
.footer-cell--mid {
  flex: 0 0 48px;
  min-width: 48px;
}

/* 右:吃掉剩余宽度,避免与左、中三等分导致长文案被裁切 */
.footer-cell--right {
  flex: 1 1 0;
  justify-content: flex-end;
}

.label-wrap {
  box-sizing: border-box;
  padding: 8px 12px 10px 12px;
  border-radius: 16px;
  background-color: rgba(255, 255, 255, 0.12);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  max-width: 100%;
  overflow: visible;
}

.label {
  color: #fff;
  font-size: 12px;
  line-height: 16px;
  margin-top: 2px;
}

.badge {
  min-width: 18px;
  height: 18px;
  padding-left: 4px;
  padding-right: 4px;
  border-radius: 9px;
  background-color: #ff0000;
  display: flex;
  justify-content: center;
  align-items: center;
}

.badge-text {
  color: #fff;
  font-size: 12px;
  font-weight: bold;
}

.flash-btn {
  width: 44px;
  height: 44px;
  border-radius: 22px;
  background-color: rgba(255, 255, 255, 0.12);
  display: flex;
  justify-content: center;
  align-items: center;
}

.flash-icon {
  width: 22px;
  height: 22px;
}

.finish-btn {
  height: 44px;
  padding-left: 14px;
  padding-right: 14px;
  border-radius: 22px;
  background-color: #1c86ee;
  display: flex;
  justify-content: center;
  align-items: center;
  box-sizing: border-box;
}

.finish-btn--loading {
  opacity: 0.7;
}

.finish-text {
  color: #fff;
  font-size: 14px;
  line-height: 20px;
  white-space: nowrap;
  text-align: center;
}
</style>

在页面中与插件配合示例:

<template #footer>
  <WxScanFooter
    :count="batchCount"
    :safe-bottom="safeBottom"
    :flash-on="flashOn"
    :loading="submitLoading"
    :finish-text="finishButtonText"
    @toggle-flash="toggleFlash"
    @finish="onFinish"
  />
</template>

通过 ref 调用

方法 说明
getCameraComponent() 内层原生 <camera> 对应实例
createCameraContext() uni.createCameraContext(...),需 cameraid 时请通过根组件透传 id
playSuccessFeedback() success 载荷中 playSuccessFeedback 一致

透传到 <camera> 的内层已绑定项(摘要)

内层对原生 <camera> 显式绑定了 classstyleidmoderesolutiondevice-positionframe-sizeoutput-dimensionflashscancodeerrorinitdonestopready 等;额外 camera 事件可通过 $attrsonXxx(Vue3)或由根组件合并的 $listeners(Vue2)传入。

文件结构(本包)

uni_modules/yx-customScan/
├── components/yx-customScan/
│   ├── yx-customScan.vue      # 根组件(对外使用)
│   └── components/
│       ├── wxScanCamera.vue   # 小程序 camera 封装
│       └── animationline.vue  # 扫描线
├── package.json
└── readme.md

内置静态资源路径由 wxScanCamera.vue 引用(如 ../static/../audio/),请保证插件目录下存在对应文件,或仅用 scanLineImage / backIconSrc / successAudioSrc 指向自有资源。

权限与体验

  • 按需配置微信小程序 摄像头 等权限说明(app.json / manifest)。
  • 真机调试时确认 隐私协议 / 用户信息授权 符合平台要求。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

暂无用户评论。