更新记录
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> 文档中的属性(如 id、device-position、resolution、frame-size、class、style、flash、mode 等),由内层读 $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 API(props / 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(...),需 camera 带 id 时请通过根组件透传 id |
playSuccessFeedback() |
与 success 载荷中 playSuccessFeedback 一致 |
透传到 <camera> 的内层已绑定项(摘要)
内层对原生 <camera> 显式绑定了 class、style、id、mode、resolution、device-position、frame-size、output-dimension、flash、scancode、error、initdone、stop、ready 等;额外 camera 事件可通过 $attrs 的 onXxx(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)。 - 真机调试时确认 隐私协议 / 用户信息授权 符合平台要求。

收藏人数:
购买源码授权版(
试用
赞赏(0)
下载 85
赞赏 0
下载 11687057
赞赏 1897
赞赏
京公网安备:11010802035340号