更新记录
0.0.1(2026-09-08)
功能
- 相机实时预览(内嵌在页面任意区域)
- 拍照,返回照片文件路径(
taken事件) - 前后摄像头切换(
facing属性 /switchCamera方法) - 闪光灯 / 手电筒(
setFlash方法) - 运行时权限自动申请,被拒时通过
error事件通知 - 预览就绪
ready事件、错误码规范化
平台兼容性
uni-app(4.0)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| × | × | × | × | × | √ | 5.0 | × | × |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| × | × | × | × | × | × | × | × | × | × | × | × |
uts-ccamera 自定义相机(Camera2)
基于 Android Camera2 API 的自定义相机 UTS 组件插件。以组件方式内嵌到页面 template 中,无需三方依赖,标准基座即可真机运行。
功能
- 相机实时预览(内嵌在页面任意区域)
- 拍照,返回照片文件路径(
taken事件) - 前后摄像头切换(
facing属性 /switchCamera方法) - 闪光灯 / 手电筒(
setFlash方法) - 运行时权限自动申请,被拒时通过
error事件通知 - 预览就绪
ready事件、错误码规范化
平台兼容性
| 平台 | 支持 | 说明 |
|---|---|---|
| app-android | √ | Android 5.0+(API 21),uni-app 需 HBuilderX 3.6.18+,uni-app x 需 HBuilderX 3.9+ |
| app-ios | × | 未实现,请勿在 iOS 页面引入 |
| app-harmony | × | 不支持(uni-app 兼容模式组件限制) |
| H5 / 小程序 | × | 未实现,请用条件编译隔离 |
⚠️ 重要:本插件为 uts 组件(uni-app 兼容模式)。uni-app 项目请在 nvue 页面中使用;uni-app x 项目请在 uvue 页面(VDOM 模式)中使用,不支持 uni-app x 蒸汽模式。
权限与原生配置
插件已在 utssdk/app-android/AndroidManifest.xml 中声明:
<uses-permission android:name="android.permission.CAMERA" />
- 运行时权限:组件在
NVLoaded阶段自动调用UTSAndroid.requestSystemPermission申请相机权限,无需前端再调用uni.authorize。用户拒绝后会触发error事件(errCode=1001),可在此时提示用户前往系统设置(UTSAndroid.gotoSystemPermissionActivity)。 - 标准基座:如真机运行时报无相机权限,请在项目
manifest.json→ App 模块配置中勾选 Camera 权限,或制作自定义基座(涉及 AndroidManifest/原生配置的变更需自定义基座才能生效)。 - 云打包:插件 manifest 会自动合并,无需额外配置。
快速开始
⚠️ 组件标签必须使用显式闭合(
></uts-ccamera>),不要用自闭合/>——uni-app x 的模板编译器对自定义组件自闭合支持不完整,会报「Element is missing end tag」。
<template>
<view>
<!-- ⚠️ 必须用 style 指定组件宽高,否则组件不显示 -->
<uts-ccamera
ref="cam"
:facing="facing"
style="width:375px;height:500px;"
@ready="onReady"
@error="onError"
@taken="onTaken"></uts-ccamera>
<button @tap="shoot">拍照</button>
</view>
</template>
组件属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| facing | String | "back" | 摄像头朝向:"back" 后置 / "front" 前置。动态修改会自动切换摄像头 |
组件事件
| 事件 | 参数(Map) | 说明 |
|---|---|---|
| ready | facing: string |
预览会话就绪,可以开始拍照 |
| error | errCode: number, errMsg: string |
发生错误,见错误码表 |
| taken | filePath: string, width: number, height: number |
拍照完成,照片已写入磁盘 |
错误码
| errCode | 含义 |
|---|---|
| 1001 | 相机权限被用户拒绝 |
| 1002 | 相机打开失败(openCamera / 设备错误) |
| 1003 | 捕获会话配置失败 |
| 1004 | 拍照请求失败 |
| 1005 | 相机未就绪 / 未找到指定朝向的摄像头 / 上下文缺失 |
| 1006 | 照片写入磁盘失败 |
组件方法
通过 ref 调用。拍照结果仅通过 taken 事件异步返回(nvue 不支持组件方法同步返回值)。
| 方法 | 参数 | 说明 |
|---|---|---|
| takePhoto() | 无 | 拍照,结果经 taken 事件返回;进行中重复调用会被忽略 |
| switchCamera(facing : string) | "back" / "front" | 切换摄像头 |
| setFlash(mode : string) | "off" / "on" / "torch" | "off" 关闭;"on" 拍照瞬间自动闪光;"torch" 手电筒常亮(预览中即时生效) |
| startPreview() | 无 | 开始/恢复预览(权限被拒后重试、停止后恢复) |
| stopPreview() | 无 | 停止预览并释放相机,建议退后台时调用 |
uni-app 项目调用示例(nvue 页面)
⚠️ 必须将页面文件命名为
xxx.nvue(不是.vue)。uts 组件只能在 nvue 页面使用;放在.vue页面时组件不被识别,$refs上没有组件方法,会报takePhoto is not a function。⚠️ 本节示例仅适用于 uni-app 项目(页面后缀
.nvue,JS 代码)。如果你是 uni-app x 项目(页面后缀.uvue),请直接使用下一节的 uvue 示例——两套代码不通用,把 JS 示例粘贴进 uvue 会报「An explicit type is required on a value parameter」等类型错误。
<template>
<view style="flex:1;">
<uts-ccamera
ref="cam"
:facing="facing"
style="width:375px;height:500px;background-color:#000000;"
@ready="onReady"
@error="onError"
@taken="onTaken"></uts-ccamera>
<view style="flex-direction:row;">
<button @tap="shoot">拍照</button>
<button @tap="flip">翻转</button>
<button @tap="torch">手电筒</button>
</view>
<text>{{ status }}</text>
</view>
</template>
<script>
export default {
data() {
return {
facing: "back",
status: ""
}
},
methods: {
// 统一调用组件方法的辅助函数:
// 兼容 ref 直调、ref 为数组包装、以及部分版本(vue3)需要 $callMethod 桥接的情况
callCam(name, ...args) {
let ref = this.$refs["cam"] || this.$refs.cam
if (Array.isArray(ref)) {
ref = ref[0]
}
if (!ref) {
console.log("未找到相机组件 ref,请确认页面为 .nvue")
return
}
if (typeof ref[name] === "function") {
ref[name].apply(ref, args)
} else if (typeof ref.$callMethod === "function") {
ref.$callMethod(name, ...args)
} else {
console.log("组件方法不可用:请确认页面文件后缀为 .nvue,且 HBuilderX ≥ 3.6.18")
}
},
shoot() {
this.callCam("takePhoto")
},
flip() {
this.facing = this.facing === "back" ? "front" : "back"
// 也可以用组件方法:this.callCam("switchCamera", "front")
},
torch() {
this.callCam("setFlash", "torch")
},
onReady() {
this.status = "相机就绪"
},
onError(e) {
// 事件参数在不同环境下可能直接是 Map/对象,也可能包在 e.detail 中,做兼容处理
const d = e.detail || e
this.status = "错误 " + d.errCode + ": " + d.errMsg
},
onTaken(e) {
const d = e.detail || e
this.status = "拍照完成"
uni.previewImage({
urls: [d.filePath]
})
}
}
}
</script>
事件参数在 uni-app(JS 环境)中经桥接转换,可能通过
e.detail访问字段,也可能直接是对象(示例已做兼容)。
进阶:自定义相机页完整示例(nvue 页面)
组件只负责出预览画面和拍照,页面 UI 完全由你用 vue 模板自由设计——取景框、快门按钮、遮罩、水印、提示文字都可以任意叠加。下面是一个完整的身份证取景风格相机页(pages/camera/camera.nvue,记得在 pages.json 中注册页面):
<template>
<view style="flex:1;background-color:#000000;">
<!-- 顶部栏 -->
<view style="height:88px;flex-direction:row;align-items:center;justify-content:space-between;padding-left:24px;padding-right:24px;">
<text style="color:#ffffff;font-size:30px;" @click="goBack">关闭</text>
<text style="color:#ffffff;font-size:30px;" @click="toggleFlash">{{ flashText }}</text>
</view>
<!-- 预览区 + 取景遮罩 -->
<view style="flex:1;align-items:center;justify-content:center;">
<view style="width:600px;height:800px;">
<uts-ccamera
ref="cam"
:facing="facing"
style="width:600px;height:800px;background-color:#111111;"
@ready="onReady" @error="onError" @taken="onTaken"></uts-ccamera>
<!-- 四角取景框(叠加在组件上层) -->
<view style="position:absolute;left:0;top:0;width:60px;height:60px;border-left-width:4px;border-top-width:4px;border-color:#00FF88;"></view>
<view style="position:absolute;right:0;top:0;width:60px;height:60px;border-right-width:4px;border-top-width:4px;border-color:#00FF88;"></view>
<view style="position:absolute;left:0;bottom:0;width:60px;height:60px;border-left-width:4px;border-bottom-width:4px;border-color:#00FF88;"></view>
<view style="position:absolute;right:0;bottom:0;width:60px;height:60px;border-right-width:4px;border-bottom-width:4px;border-color:#00FF88;"></view>
</view>
<text style="color:#ffffff;font-size:26px;margin-top:24px;">{{ tipText }}</text>
</view>
<!-- 底部操作栏 -->
<view style="height:180px;flex-direction:row;align-items:center;justify-content:space-around;">
<text style="color:#ffffff;font-size:28px;width:120px;text-align:center;" @click="flip">翻转</text>
<view @click="shoot" style="width:140px;height:140px;border-radius:70px;background-color:#ffffff;align-items:center;justify-content:center;">
<view style="width:110px;height:110px;border-radius:55px;background-color:#FF3B30;"></view>
</view>
<text style="color:#ffffff;font-size:28px;width:120px;text-align:center;" @click="torch">手电</text>
</view>
</view>
</template>
<script>
export default {
data() {
return {
facing: "back",
flashMode: "off",
tipText: "正在启动相机..."
}
},
computed: {
flashText() {
return this.flashMode === "torch" ? "⚡ 已开" : "⚡ 关闭"
}
},
methods: {
callCam(name, ...args) {
let ref = this.$refs["cam"] || this.$refs.cam
if (Array.isArray(ref)) { ref = ref[0] }
if (!ref) { return }
if (typeof ref[name] === "function") {
ref[name].apply(ref, args)
} else if (typeof ref.$callMethod === "function") {
ref.$callMethod(name, ...args)
}
},
shoot() {
this.tipText = "拍照中..."
this.callCam("takePhoto")
},
flip() {
this.facing = this.facing === "back" ? "front" : "back"
this.tipText = "切换摄像头..."
},
torch() {
this.flashMode = this.flashMode === "torch" ? "off" : "torch"
this.callCam("setFlash", this.flashMode)
},
toggleFlash() {
this.torch()
},
goBack() {
uni.navigateBack()
},
onReady() {
this.tipText = "请将证件放入取景框"
},
onError(e) {
const d = e.detail || e
this.tipText = "错误 " + d.errCode + ":" + (d.errCode === 1001 ? "请授予相机权限后重试" : d.errMsg)
if (d.errCode === 1001) {
this.callCam("startPreview") // 可在此弹出引导后重试
}
},
onTaken(e) {
const d = e.detail || e
this.tipText = "已保存:" + d.filePath
// 实际业务中可在此上传:uni.uploadFile({ filePath: d.filePath, ... })
}
}
}
</script>
nvue 布局提示:nvue 仅支持 flex 布局,默认
flex-direction: column;尺寸单位 px 为逻辑像素(默认 750px 等于屏宽);叠加层用position: absolute。更多 nvue 样式约束见官方文档。
uni-app x 项目调用示例(uvue 页面,VDOM 模式)
⚠️ 本节示例仅适用于 uni-app x 项目(页面后缀
.uvue,script lang="uts"强类型代码)。uni-app 项目(.nvue页面)请使用前面的 nvue 示例,两套代码不通用。
<template>
<view style="flex:1;">
<uts-ccamera
id="camera"
ref="cam"
:facing="facing"
style="width:375px;height:500px;background-color:#000000;"
@ready="onReady"
@error="onError"
@taken="onTaken"></uts-ccamera>
<button @click="shoot">拍照</button>
</view>
</template>
<script lang="uts">
// Element 类名 = 组件名转 upper camel case + Element,模块名 = 插件目录名转 lower camel case
// 若 HBuilderX 自动提示的名称不同,以实际生成为准
import { UtsCcameraElement } from 'uts.sdk.modules.utsCcamera'
export default {
data() {
return {
facing: "back"
}
},
methods: {
shoot() {
(this.$refs["cam"] as UtsCcameraElement).takePhoto()
// 也可以: (uni.getElementById("camera") as UtsCcameraElement).takePhoto()
},
onReady() {
console.log("相机就绪")
},
onError(e : Map<string, any>) {
console.log("错误码:", e.get('errCode'), "信息:", e.get('errMsg'))
},
onTaken(e : Map<string, any>) {
let path = e.get('filePath') as string
uni.previewImage({
urls: [path]
})
}
}
}
</script>
uni-app x 的 Android 端事件参数为
Map<string, any>,用e.get('字段名')取值。
注意事项 / 已知限制
- 必须用 style 指定组件宽高,否则组件不显示(uts 组件通用要求)。
- 同一页面同时仅支持一个实例(v1 限制)。多个实例时后创建的会覆盖前一个的内部状态。
- 照片保存在应用外部私有目录
Android/data/<包名>/files/Pictures/uts-ccamera/,无需存储权限,卸载应用即删除。返回的filePath为绝对路径,可直接用于image组件 src、uni.previewImage、uni.uploadFile等。 - 拍照方向按竖屏场景计算(JPEG_ORIENTATION 已处理传感器方向与屏幕旋转);横屏场景请自行扩展
computeJpegOrientation。 - 暂不支持:视频录制、手动对焦/变焦/曝光调节、EXIF 信息写入。
- 退后台建议调用
stopPreview(),回前台调用startPreview(),避免相机被系统回收或与其他应用冲突。 - iOS / 小程序 / H5 未实现,请在这些平台用条件编译移除相关代码。
- uni-app x 蒸汽模式不支持 uni-app 兼容模式组件,请使用 VDOM 模式项目。
FAQ
Q:调用组件方法报 takePhoto is not a function?
A:按顺序排查:1) 页面文件后缀必须是 .nvue(uts 组件不支持 .vue 页面,这是最常见原因,此时组件标签也不会被识别渲染);2) 若 console.log(this.$refs["cam"]) 输出 undefined,检查 ref 名称拼写;3) vue3 项目部分版本 ref 代理不直通原生方法,改用 ref.$callMethod('takePhoto')(readme 示例中的 callCam 辅助函数已自动兼容);4) 确认 HBuilderX ≥ 3.6.18。
Q:预览黑屏?
A:按顺序排查:1) 监听 error 事件,errCode=1001 是权限被拒;2) 确认页面是 nvue(uni-app 项目);3) 确认组件 style 指定了非零宽高;4) 模拟器/设备是否有摄像头;5) 在 ready 触发前调用 takePhoto 会返回 1005,属正常保护。
Q:标准基座运行报权限错误? A:插件 manifest 中的权限声明需自定义基座才能完整生效。临时方案:在项目 manifest.json 中勾选 Camera 权限;正式方案:制作自定义基座。
Q:与 uni.createCameraContext(camera 组件)的区别?
A:官方 camera 组件功能固定(受系统相机封装限制);本插件直接基于 Camera2,可完全控制预览输出尺寸、拍照请求参数(闪光灯、方向等),适合需要深度定制取景框、水印、连拍等场景的开发者。
Q:.vue 页面能用组件吗?想要完全自定义的相机 UI 怎么做?
A:不能。.vue 页面是 WebView 渲染,放不进原生相机画面,uts 组件只支持 nvue 页面(uni-app)——这是平台架构限制。需要完全自定义的相机界面(取景框、快门、水印等自由排版)时,新建一个 nvue 页面承载 <uts-ccamera> 组件即可,页面 UI 用 vue 模板随意设计,见上文「进阶:自定义相机页完整示例」;从 .vue 页面 uni.navigateTo 跳转到该 nvue 相机页是标准用法。
Q:照片方向不对?
A:本插件按竖屏优化。横屏使用时请修改 index.vue 中 computeJpegOrientation 的 rotation 处理逻辑。
A:本插件按竖屏优化。横屏使用时请修改 index.vue 中 computeJpegOrientation 的 rotation 处理逻辑。

收藏人数:
购买普通授权版(
试用
赞赏(0)
下载 1
赞赏 0
下载 12581294
赞赏 1949
赞赏
京公网安备:11010802035340号