更新记录
0.6.4(2026-10-01)
本版是 0.6 大版本的整合发布说明:汇集 0.6.0~0.6.3 的全部内容,无代码变更(已安装 0.6.3 的用户无需升级)。DCloud 市场「更新记录」只展示发布过的版本,故在此完整汇总本次大版本改动。
新增
- 外置水印:模板面板新增「外置」分类(页签最左),宿主经
externalTemplatesLoader异步回调或templates入参注入自家水印模板,相机一打开即后台预加载,与 8 套内置版式共存,后台可随时调整版式 - 免相机烧录三 API:
burnWatermark静默烧录(单张/批量/远程图)、editWatermarkPhoto独立打开编辑器改图、generateWatermarkOverlay出透明底 PNG 贴片(供 Web 端/服务端合成),不开相机也能给已有图片加水印 - project-4(GPS 存证)版式开放 4 个
style布局参数:logoGap/padding/lineSpacing/fontSize,外置模板可按需微调卡片几何 - 水印编辑器增强:点按水印卡片直接编辑字段、地点字段「获取」弹窗(自动定位 / 地图选点 / 手动输入)、编辑器偏好记忆(下次打开自动套用上次设置)
- 跨会话定位缓存:上次定位地址进相机即用、后台刷新后替换,外部烧录未传地址时同样兜底
修复
- 拍照定格画面与成片不一致:单拍按下快门立即冻结预览,静止画面与最终成片同源同帧(构图/旋转/前置镜像一致)
- 「修改水印内容」保存时留空字段被清空(会抹掉自动定位回填的坐标):双端统一为「留空 = 未修改、保持当前值」
- 手动改过地址后含 GPS 坐标行的水印永远补不上经纬度:手动地址缓存不再挡住自动定位补坐标
- 外置模板字段
fallbackValue不被解析:编辑表单留空时恢复预填提示 - 烧录成片丢 Logo、GPS 存证组合体越出照片底边、iOS「移除水印」后仍烧默认模板等稳定性问题
优化
- 双端原生代码全面重构与中文注释补全(行为不变);自动化测试体系上线(静态契约校验器 + 双端单元测试 + Android 真机冒烟脚本),版本质量更稳
- 水印字段空值行过滤规则全版式统一:空行不再留占位,相机预览/烧录成片/编辑器表现一致
- 「修改水印内容」表单详细地址改多行输入,长地址不再被单行截断
注意
- 内置模板删除「04 GPS 存证」,原 5~9 顺次前移为 4~8:设备上已选模板自动迁移、升级无感;GPS 存证版式仍保留,外置模板
preset: "project-4"可继续使用 - 相机链路
templates入参语义变更:接口模板从「整体替换内置列表」改为并入「外置」分类;burnWatermark/generateWatermarkOverlay/editWatermarkPhoto三 API 语义不变
0.6.3(2026-10-01)
- project-4(GPS 存证)版式开放 4 个
style几何参数(仅该版式生效):logoGapLogo 与卡片间距(默认 24,0~48)、padding卡片内边距(默认 56,0~80)、lineSpacing字段行间距(默认 36,0~48)、fontSize正文字号(默认 38,20~48) - 上述参数全部可选:缺省渲染结果与旧版完全一致;负数、非数字、超范围一律静默回落默认值,其它 preset 与未知
style字段忽略 - 修复「修改水印内容」保存时留空字段被显式清空(留空会抹掉自动定位回填的坐标等生效值):双端相机与编辑器所有保存点统一为「留空 = 未修改、保持当前值」,与表单「留空则使用水印上的默认内容」提示一致
0.5.25(2026-08-24)
- 修复本地照片烧录与相机里所见不一致:此前烧录只回退到内置第一套版式与接口入参值,相机里选中的版式、编辑过的时间/字段内容、拖过的水印位置全部丢失。现在相机让位给宿主相册前会把会话水印状态(选中版式、字段值、显隐、位置比例)写成一次性快照,烧录方读后即删并按快照还原,Android/iOS 行为一致;无会话快照时(外部直接打开编辑器)维持原行为,公开 API 不变,宿主零适配
- 修复相机关闭后再次打开报「已有相机任务正在执行」(9013002)只能杀进程:busy 标记原本只在 Activity 结果回调送达时清除,相机/编辑器异常销毁(系统回收等)会让它永久卡住。现在打开前会查原生侧相机/编辑器是否真实存活,不存在则按「用户取消」给上一次调用收尾后继续打开;同时相机 Activity/控制器没交付结果就被销毁时会主动兜底收尾,两道防线双端同构
- 修复 iOS 端
weather声明为let导致「天气随定位回传覆盖」云打包编译失败(cannot assign to property)
平台兼容性
uni-app(3.99)
| Vue2 | Vue3 | Vue3插件版本 | Chrome | Safari | app-vue | app-vue插件版本 | app-nvue | app-nvue插件版本 | Android | Android插件版本 | iOS | iOS插件版本 | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| √ | √ | 0.4.49 | - | - | √ | 0.4.49 | √ | 0.4.49 | √ | 0.4.49 | 12 | 0.4.49 | × |
| 微信小程序 | 微信小程序插件版本 | 支付宝小程序 | 支付宝小程序插件版本 | 抖音小程序 | 抖音小程序插件版本 | 百度小程序 | 百度小程序插件版本 | 快手小程序 | 快手小程序插件版本 | 京东小程序 | 京东小程序插件版本 | 鸿蒙元服务 | 鸿蒙元服务插件版本 | QQ小程序 | QQ小程序插件版本 | 飞书小程序 | 飞书小程序插件版本 | 小红书小程序 | 小红书小程序插件版本 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| √ | 0.4.49 | √ | 0.4.49 | √ | 0.4.49 | √ | 0.4.49 | √ | 0.4.49 | √ | 0.4.49 | √ | 0.4.49 | √ | 0.4.49 | √ | 0.4.49 | √ | 0.4.49 | × | × |
uni-app x(3.99)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| - | - | - | - | - | - |
其他
| 多语言 | 暗黑模式 | 宽屏模式 | 蒸汽模式 |
|---|---|---|---|
| √ | √ | × | √ |
zoo-watermark-camera
自绘相机预览,水印卡片实时叠加、所见即所得;按快门把水印烧进照片像素,落盘即可直接 uni.uploadFile。对标施工现场水印相机。
系统相机拍出来的是一张干净照片,时间、地点、工程名、拍摄人这些「存证信息」都要后期 P 上去——这个插件解决的就是这件事:拍照/录像的当下,水印直接烧进成片。
特性
- 实时水印预览:水印卡片叠加在取景区上,拖动即所见,烧录位置与预览一致;拖动落点本地记住,下次打开自动恢复
- 8 套内置版式:工程(安装服务记录 / 工程记录 / 质量复核)、时间(大字时刻 / 简洁时刻)、打卡(考勤打卡 / 打卡时刻)、巡检(维修前后),配色跟随主题色
- 外置水印:相机模板面板带独立「外置」分类,宿主通过
externalTemplatesLoader异步回调或templates入参注入自家模板,相机一打开就后台预加载,与内置版式共存互不影响;还可显式复用内置排版(含 GPS 存证版式) - 单份 JSON 驱动:宿主传入一份
watermarkData,watermarkId精确匹配内置版式,字段给值即显示。字段行空值规则全版式统一:label 和值都空(或纯值行值空)整行不渲染,有 label 的行值空保留「label:」形态 - 拍照 + 录像双烧录:照片烧录为 JPG,录像输出已烧录水印的 MP4
- 本地照片烧录:从系统相册或 zoo-media-picker 选已有照片,按当前水印配置自动生成成片
- 外部烧录三 API:不开相机/编辑器——
burnWatermark静默烧录(支持批量与远程图)、editWatermarkPhoto独立打开编辑器改图、generateWatermarkOverlay出透明底 PNG 贴片供 Web 端/服务端合成 - 编辑器偏好记忆:编辑器记住用户上次的模板/大小/透明度/位置,下次打开自动套用;插件内本地持久化,宿主零接入成本
- 跨会话定位缓存:上次定位地址持久化,进相机即用、后台刷新后替换;外部烧录同样受益
- 连拍模式:一次进相机连续拍摄、当场剔除废片,确认后一次性输出全部成片;暂存跨模式、跨会话保留
- 双端原生:Android CameraX / iOS AVFoundation,零第三方图像库依赖,不和宿主已有库打架
- 可保留原图:设置内开启后回调同时返回
[原始文件, 水印成片],resultType区分 - 完整相机能力:闪光灯(关/开/自动/常亮)、前后摄、点按对焦、捏合变焦、0.5x/1x/2x、九宫格、拍摄音效与振动反馈
目录
- 快速开始 · 兼容版本与基座边界
- 水印数据(watermarkData)——8 套内置版式 ID 与字段表
- 接口配置水印(宿主样式接口)——
templates入参与模板 JSON 结构 - 外置水印(externalTemplatesLoader)——「外置」分类、loader 用法、project-4 几何参数
- 参数 · 返回 · 错误码
- 外部烧录(不开相机)——burnWatermark / editWatermarkPhoto / generateWatermarkOverlay
- 编辑器偏好记忆 · 运行时定位 Provider
- JSON 配置中心 · 本地照片与 zoo-media-picker 适配器 · 连拍模式
- 宿主权限声明 · FAQ · 授权说明
快速开始
import { openWatermarkCamera } from '@/uni_modules/zoo-watermark-camera'
openWatermarkCamera({
mode: 'photo', // photo | video
watermark: { scale: 0.9, opacity: 0.72, position: 'lb' },
watermarkData: {
watermarkId: 1, // 匹配「安装服务记录」版式
watermarkName: '工作巡查记录', // 覆盖该版式的默认名称
title: '3 楼外机清洗',
content: '设备日常巡检',
weather: '晴 26℃',
location: {
gpsWgs84: '120.123456,30.123456',
addressFull: '示例省示例市示例区示例路 88 号示例大厦',
addressArea: '示例市示例区'
},
unitName: '示例服务单位',
photographer: '张三',
remark: '外机深度清洗完成',
themeColor: '#1677FF',
custom: { '工单号': 'WO-2026-001' },
allowEditTime: false,
allowEditLocation: false
},
theme: { primaryColor: '#3B82F6' },
success: (res) => {
// res.file 始终指向最终成片,可直接上传
uni.uploadFile({ url: 'https://your.host/upload', filePath: res.file.tempFilePath, name: 'file' })
},
fail: (err) => {
console.log(err.errCode, err.errMsg)
}
})
仅支持 App(Android / iOS),H5 与小程序不支持。
兼容版本与基座边界
- 最低 HBuilderX:3.99。Android 最低版本:API 21(Android 5.0);iOS 最低版本:iOS 12。
- 本插件是 UTS 原生插件,不支持标准基座,必须用自定义基座真机运行或云打包。
- Android 的 CameraX 是插件声明的 Maven 三方依赖,必须随自定义基座一起构建;新增/升级依赖后必须重新制作基座,并把宿主
manifest.json的versionName/versionCode递增,避免复用不含新依赖的旧基座。 - 若运行日志出现「手机端自定义基座已是最新版本,跳过更新」但插件刚升级过依赖,请重新制作基座;仍被跳过时,卸载手机上的旧 App 后再安装新基座。
水印数据(watermarkData)
watermarkData 是一份固定结构的 JSON。样式只由 watermarkId 精确匹配;watermarkName 只覆盖命中版式的默认名称,不参与样式匹配。
内置版式 ID(0.6.1 起为 8 套:删除了原「4 GPS 存证」,原 5~9 顺次前移为 4~8;GPS 存证版式仍保留在渲染器中,可由外置模板通过 preset: 'project-4' 复用,见「外置水印」一节):
| ID | 版式 | 分类 |
|---|---|---|
1 |
安装服务记录 | 工程 |
2 |
工程记录 | 工程 |
3 |
质量复核 | 工程 |
4 |
大字时刻 | 时间 |
5 |
简洁时刻 | 时间 |
6 |
考勤打卡 | 打卡 |
7 |
打卡时刻 | 打卡 |
8 |
维修前后 | 巡检 |
字段说明(每套版式按版式合理取值,字段不要求全部渲染,未传或为空不显示):
| 字段 | 类型 | 含义与控制逻辑 |
|---|---|---|
watermarkId |
number | 必填。精确匹配内置版式;未命中时不渲染该份动态水印 |
watermarkName |
string | 覆盖命中版式的默认名称;为空时保留默认名 |
title |
string | 水印标题;版式预留标题位时才显示,同时喂给工程类版式的工程名称 |
content |
string | 施工 / 巡检内容;工程与巡检类版式使用 |
weather |
string | 天气;版式有天气位时显示,并作为相机内当前天气值 |
location |
string / object | 初始拍摄地点。新接入推荐传下文的结构化对象并显式标注 coordinateSystem;gpsWgs84、addressFull、addressArea 和普通字符串继续作为旧格式兼容输入。能否在相机内修改由 allowEditLocation 决定 |
logoUrl |
string | Logo 图片,支持 http(s) 链接与本地路径(file:// / 绝对路径);仅带 Logo 位的版式使用,加载失败回退默认图形 |
unitName |
string | 单位名称;仅带单位名称位的版式使用 |
photographer |
string | 拍摄人名称;仅带拍摄人位的版式使用 |
photographerAvatar |
string | 拍摄人头像,支持 http(s) 链接与本地路径;仅带头像位的版式使用,加载失败回退默认图形 |
remark |
string | 备注;仅带备注位的版式使用 |
themeColor |
string | #RRGGBB 主题色;覆盖命中版式的整套配色。非法或为空时保留版式默认色 |
custom |
object | 自定义扩展字段,键和值均为字符串;作为额外文本行按需取用,空值不渲染 |
allowEditTime |
boolean | true 时相机内可编辑拍摄时间;false 时显示设备当前时间且锁定 |
allowEditLocation |
boolean | true 时地点可编辑、定位图标可打开地点面板;false 时锁定 |
接口配置水印(宿主样式接口)
水印样式不只靠 watermarkData 匹配内置版式:宿主可以从自家接口拉取一整组模板配置,通过 templates + defaultTemplateId 传入,openWatermarkCamera、editWatermarkPhoto、burnWatermark、generateWatermarkOverlay 四个入口同构支持。
templates 语义按链路区分(0.6.1 起):
- 相机链路(
openWatermarkCamera):解析成功的接口模板不再整体替换内置列表,而是统一并入模板面板的「外置」分类(强制group="external"),追加在 8 套内置模板之后,与内置共存;与externalTemplatesLoader异步回传同等待遇,两者都给则合并、按id去重(loader 后入,同id覆盖)。相机链路更推荐使用「外置水印」一节的externalTemplatesLoader,相机打开即后台预加载,不用宿主先拉完接口再开相机。 - 烧录/编辑器链路(
burnWatermark/generateWatermarkOverlay/editWatermarkPhoto):保持原有整体替换语义(强制group="config"),接口模板替换内置列表。
相机内与编辑器内都支持多模板切换,编辑器内点按水印卡片可编辑未锁定的字段。
插件不依赖任何特定后端:模板 JSON 由宿主透传(任意来源——自家接口、本地配置、硬编码示例均可),插件只认本节的数据结构,不发起任何网络请求、不绑定任何服务地址。
推荐接口形状(GET /api/watermark-templates/app/enabled 的 data):
{
"defaultTemplateId": "cfg-project-record",
"templates": [
{
"id": "cfg-project-record",
"name": "工程记录(接口)",
"group": "config",
"category": "工程",
"preset": "project-record",
"style": { "themeColor": "#1677FF", "opacity": 0.82, "corner": 14 },
"fields": [
{ "key": "projectName", "label": "工程名称", "valueType": "input", "value": "示例设备维护工程", "editable": true, "required": true, "maxLength": 30 },
{ "key": "time", "label": "拍摄时间", "valueType": "datetime", "editable": true },
{ "key": "location", "label": "地点", "valueType": "location", "editable": true, "required": true },
{ "key": "unit", "label": "施工单位", "valueType": "input", "value": "示例服务单位", "editable": true, "maxLength": 20 },
{ "key": "remark", "label": "备注", "valueType": "input", "editable": true, "placeholder": "填写现场备注", "maxLength": 40 }
],
"placement": { "scale": 0.9, "opacity": 0.85, "position": "lb" }
}
]
}
宿主接入(完整可跑示例见 demo 页「接口配置水印」区块):
import { openWatermarkCamera, editWatermarkPhoto } from '@/uni_modules/zoo-watermark-camera'
const { data } = await yourRequest('/api/watermark-templates/app/enabled')
// placement 是宿主消费字段,插件不读:从默认模板取出后传给 watermark 入参,templates 数组里不带它。
const templates = data.templates.map(({ placement, ...tpl }) => tpl)
const current = data.templates.find(t => t.id === data.defaultTemplateId) || data.templates[0]
openWatermarkCamera({
templates, // 不要再同时传 watermarkData:watermarkData 优先级更高,会覆盖 templates
defaultTemplateId: data.defaultTemplateId,
watermark: { ...current.placement }, // 建议落位;用户在相机内拖动/缩放后会覆盖初始值
success: (res) => {}, fail: (err) => {}
})
editWatermarkPhoto({
filePath: absolutePath,
templates,
defaultTemplateId: data.defaultTemplateId,
watermark: { ...current.placement },
// 可选:编辑器地点字段「获取」弹窗里的「地图选点」入口。
// 不注入时弹窗只有「自动获取当前位置」(系统定位+逆地理)和「手动输入」两项。
externalPicker: {
locationPicker: (success, fail) => {
// 真实项目用 uni.chooseLocation 或自家地图页;完成后把结果交给 success。
uni.chooseLocation({
success: (res) => success({ address: res.address || res.name || '', name: res.name, latitude: res.latitude, longitude: res.longitude }),
fail: (err) => fail((err && err.errMsg) || '用户取消选择地点')
})
}
},
success: (res) => {}, fail: (err) => {}
})
locationPicker 的 success 回调接收 ZooLocationPickerResult:
| 字段 | 类型 | 说明 |
|---|---|---|
address |
string | 完整地址;空串时编辑器回落用 name 回写 |
name |
string | 地点名称,address 为空时的兜底 |
latitude / longitude |
number | 坐标(可选,编辑器目前只展示地址文本) |
weather |
string | 天气文案(可选);有值时一并回写水印天气字段,缺省不动 |
模板字段语义:
| 字段 | 类型 | 说明 |
|---|---|---|
id |
string | 必填。模板唯一 id,空串会被丢弃;defaultTemplateId 按它命中默认模板 |
name |
string | 模板名称,显示在相机/编辑器的模板切换列表里 |
group |
string | 固定由插件按链路强制覆盖,传入值不生效:相机链路为 'external'(外置),烧录/编辑器链路为 'config' |
category |
string | 相机模板面板的分类标签;相机链路的外置模板统一归入「外置」分类 |
preset |
string | 版式:time-loc-weather / project-record / attendance / anti-theft / custom,决定排版骨架;另可显式传 'project-4' 复用 GPS 存证版式(原始经纬度 + 每秒走动的实时时钟,0.6.1 起不再内置、仅供外置模板使用) |
style.themeColor / titleBarColor / bodyColor |
string | #RRGGBB 配色覆盖,缺省回落 preset 默认值 |
style.opacity |
number | 模板默认不透明度 0.25-1;watermark.opacity 可在每次调用时覆盖 |
style.corner |
number | 卡片圆角(Android dp / iOS pt),0-40 |
fields[].key / label |
string | 字段键与显示名;projectName/title/brand 会被当作标题位,unit 为底条单位位 |
fields[].valueType |
string | static 静态文本 / input 可输入 / datetime 拍摄时间 / location 地点 / weather 天气;input 默认 editable: true |
fields[].value |
string | 字段初始值;datetime 留空时取设备当前时间,用户编辑后按填写值烧录 |
fields[].visible |
boolean | 是否渲染该字段,默认 true |
fields[].editable |
boolean | 是否允许在相机/编辑器内改值;locked: true 的字段即使 editable 也不可改 |
fields[].required |
boolean | 编辑表单保存时非空校验 |
fields[].maxLength / placeholder |
number / string | 输入长度上限(默认 100)与占位提示 |
fields[].fallbackValue |
string | 演示兜底值:无接口数据时的预填内容与编辑表单的留空 hint(hint 优先取 placeholder);0.6.2 起接口模板也解析该字段,与内置模板字段同一语义 |
placement |
object | 宿主消费字段:建议的 scale/opacity/position,由宿主取出后传给 watermark 入参,插件不解析模板内的 placement |
外置水印(externalTemplatesLoader)
相机模板面板的分类页签固定为「外置 / 全部 / 工程 / 时间 / 打卡 / 巡检」,「外置」排最左——它是宿主的专属扩展位:通过 openWatermarkCamera 新增的 externalTemplatesLoader 入参传入一个异步加载函数,相机一打开就在后台自动调起它(不等用户点进「外置」页签),回传的模板渲染进「外置」分类,与 8 套内置版式共存——外置是扩展,不替换内置。
适合:水印模板由自家后台运营下发、按租户/项目动态变化、不想把模板硬编码进 App 的场景。与「接口配置水印」一样,插件只认模板 JSON 结构,不绑定任何后端。
接入步骤
- 准备一个异步函数,签名固定为
(success, fail) => void:完成自己的加载(接口请求、本地配置读取均可)后调success(模板数组);失败调fail('可读文案')。 - 打开相机时把它传给
externalTemplatesLoader:
import { openWatermarkCamera } from '@/uni_modules/zoo-watermark-camera'
// 方式一:从自家服务器拉取
const externalTemplatesLoader = (success: (templates: any[]) => void, fail: (message: string) => void) => {
uni.request({
url: 'https://your.host/api/watermark-templates/app/enabled',
success: (res) => {
const list = (res.data as any)?.data?.templates
if (Array.isArray(list)) {
// placement 是宿主消费字段,插件不读:回传前剔除即可
success(list.map(({ placement, ...tpl }) => tpl))
} else {
fail('模板数据格式异常')
}
},
fail: () => fail('网络异常,模板加载失败')
})
}
openWatermarkCamera({
externalTemplatesLoader,
success: (res) => {}, fail: (err) => {}
})
// 方式二:本地静态数组(无接口时也能用)
const externalTemplatesLoader = (success: (templates: any[]) => void, fail: (message: string) => void) => {
success([
{
id: 'ext-project-record',
name: '工程记录(外置)',
preset: 'project-record',
style: { themeColor: '#1677FF', opacity: 0.82, corner: 14 },
fields: [
{ key: 'projectName', label: '工程名称', valueType: 'input', value: '示例设备维护工程', editable: true, required: true, maxLength: 30 },
{ key: 'time', label: '拍摄时间', valueType: 'datetime', editable: true },
{ key: 'location', label: '地点', valueType: 'location', editable: true, required: true },
{ key: 'unit', label: '施工单位', valueType: 'input', value: '示例服务单位', editable: true, maxLength: 20 }
]
},
{
// 显式复用 GPS 存证版式:原始经纬度 + 每秒走动的实时时钟
id: 'ext-gps-evidence',
name: 'GPS 存证(外置)',
preset: 'project-4',
style: { themeColor: '#1677FF' },
fields: [
{ key: 'gpsWgs84', label: '经纬度', valueType: 'location', locked: true },
{ key: 'time', label: '拍摄时间', valueType: 'datetime', locked: true },
{ key: 'photographer', label: '拍摄人员', valueType: 'input', editable: true }
]
}
])
}
完整可跑示例见 demo 页「接口配置水印」区块的「打开相机(loader 异步加载)」用例(mockExternalTemplatesLoader,setTimeout 800ms 模拟接口耗时后回传两套模板)。
返回模板 JSON 逐字段说明
success 回传的模板元素结构与「接口配置水印」一节的模板 JSON 完全一致,数组可空(空数组表示没有外置模板):
| 字段 | 必填 | 说明 |
|---|---|---|
id |
必填 | 模板唯一 id,空串会被丢弃;defaultTemplateId 按它命中默认模板;与 templates 入参合并时按 id 去重,loader 结果覆盖同 id 项 |
name |
建议必填 | 外置模板卡片直接用它做展示名(不拼编号) |
preset |
必填 | 排版骨架:time-loc-weather / project-record / attendance / anti-theft / custom;另可显式传 'project-4' 复用 GPS 存证版式(该版式 0.6.1 起不再内置,渲染器保留分支专供外置模板使用) |
style |
可选 | themeColor(#RRGGBB)/ titleBarColor / bodyColor 配色覆盖、opacity(0.25-1)、corner 圆角;缺省回落 preset 默认值 |
fields |
必填 | 字段数组,逐字段语义(key/label/valueType/visible/editable/locked/required/maxLength/placeholder)见「接口配置水印」一节的模板字段语义表 |
group / category |
不需要传 | 相机链路里由插件强制为 'external' / 「外置」,传入值会被覆盖 |
placement |
可选 | 宿主消费字段,插件不读;建议落位请取出后传给 watermark 入参 |
project-4 几何尺寸(style,仅 GPS 存证版式生效)
preset: 'project-4' 的模板还可以在 style 里覆盖 4 个几何尺寸参数(其它 preset 一律忽略,未知 style 字段同样忽略):
| 参数 | 含义 | 单位 | 默认值 | 接受范围 |
|---|---|---|---|---|
style.logoGap |
Logo 与下方内容卡片的间距;调大即把 Logo 相对卡片往上挪 | 渲染器设计坐标(随缩放等比作用于成片,与 dp 同比例语义) | 24 | 0~48 |
style.padding |
内容卡片四边内边距 | 同上 | 56 | 0~80 |
style.lineSpacing |
字段行间距 | 同上 | 36 | 0~48 |
style.fontSize |
正文字号(经纬度行超宽缩字号的下限跟随它) | 同上(与 sp 同比例语义) | 38 | 20~48 |
规则:全部可选,缺省不渲染结果与旧版逐像素一致;负数、非数字、超出接受范围一律静默回落默认值,不会让坏 JSON 影响布局。注意行内行距(多行文本折行后的行距)不在这 4 个参数里,未开放。
「把 Logo 往上挪一点」示例(logoGap 24 → 40):
{
"id": "ext-gps-evidence",
"name": "GPS 存证(外置)",
"preset": "project-4",
"style": { "logoGap": 40 },
"fields": [
{ "key": "gpsWgs84", "label": "GPS 坐标", "valueType": "location", "fallbackValue": "113.123456,23.123456" },
{ "key": "addressFull", "label": "详细地址", "valueType": "location" },
{ "key": "photographer", "label": "拍摄人员", "valueType": "input", "editable": true },
{ "key": "timestamp", "label": "拍摄时间", "valueType": "datetime" }
]
}
加载时机与状态行为
- 时机:相机一打开就后台调起 loader,不阻塞相机使用;回传到达时若模板面板正开着,「外置」分类就地刷新。
- 三态文案(「外置」页签内):加载中显示「外置水印加载中…」;
fail回传后显示「加载失败,点击重试」(蓝色可点,点了重新触发一次 loader);成功但空数组显示「暂无外置水印」。 - 次数与缓存:一次相机会话只自动拉取一次(用户主动点重试除外);结果不持久化,关闭相机即丢弃,下次打开相机会重新调起 loader。
success/fail由插件长期持有,宿主异步多久回传都可以;相机关闭后的迟到回传会被丢弃。
常见问题
Q:loader 失败了会影响内置模板吗? 不会。外置拉取失败只影响「外置」分类的展示(显示可重试的失败态),8 套内置模板照常可用;loader 抛异常或回传非法 JSON 同样只损失外置分类。
Q:外置模板每次打开相机都要重新拉吗? 是。结果只在当前会话内有效、不落盘缓存,保证后台改模板后下次打开即生效。想省流量请在宿主侧自行缓存后在 loader 里回传缓存结果。
Q:externalTemplatesLoader 和 templates 入参有什么区别?
两者殊途同归——都进「外置」分类,可同传合并(去重规则见「接口配置水印」一节)。区别在于时机:templates 要求开相机前数据已就绪,externalTemplatesLoader 由相机打开后后台异步拉取,不拖慢开相机。新接入推荐 loader。
Q:外置模板(loader / templates)和 watermarkData 有什么区别?
两个正交概念:watermarkData 是「固定内置版式 + 动态字段数据」——用 watermarkId 命中 8 套内置版式之一,往里面填本次拍摄的值;loader / templates 是「版式扩展」——给相机增加内置之外的新模板(自定义字段、配色、排版骨架)。注意 watermarkData 优先级高于 templates:同传时 watermarkData 生效。
参数(openWatermarkCamera)
| 参数 | 类型 | 说明 |
|---|---|---|
mode |
string | 'photo'(默认)或 'video';连拍只能在相机内切入,不作为入参 |
watermark.scale |
number | 水印卡片缩放,默认 1.0,夹在 [0.5, 2] |
watermark.opacity |
number | 整个水印的最终不透明度,夹在 [0.25, 1] |
watermark.position |
string | 'lt'/'tc'/'rt'/'lb'/'bc'/'rb',默认 'lb' |
watermark.xRatio / watermark.yRatio |
number | 精确定位(可选):水印左上角在成片上的比例坐标 0~1,给了优先于 position |
watermarkData |
object | 水印数据 JSON,见上一节 |
templates |
array | 接口配置模板列表,见「接口配置水印」一节;相机链路并入「外置」分类(不再替换内置),与 watermarkData 同传时后者优先 |
externalTemplatesLoader |
function | 外置水印模板异步加载器 (success, fail) => void,相机打开即后台调起,结果并入「外置」分类;见「外置水印」一节 |
location.address |
string | 拍摄地点(宿主预先传入),未传显示「未授权位置」 |
location.weather |
string | 天气(宿主预先传入) |
theme.primaryColor |
string | 水印卡片标题/底条主题色,#RRGGBB |
theme.surfaceColor / theme.onSurfaceColor |
string | 相机上下功能区背景色/前景色 |
defaultTemplateId |
string | 默认选中的模板 id;缺省为内置工程记录 |
success / fail / complete |
function | 回调 |
返回
success 回调收到 res.file(最终成片)与可选的 res.files(多文件场景):
res = {
captureMode: string, // 确认返回时相机所处模式:'photo' | 'video' | 'burst'
// 连拍确认(哪怕只拍 1 张)报 'burst';本地照片烧录报 'photo'
keepOriginalPhoto: boolean, // 输出时「保留原图」设置是否开启
keepOriginalVideo: boolean, // 输出时「保留原视频」设置是否开启
file: { ... }, // 最终成片,结构同下
files: [ { ... } ] // 完整结果
}
res.file = {
tempFilePath: string, // 沙盒临时文件绝对路径,可直接 uni.uploadFile
previewUrl?: string, // 仅预览使用;Android 为受限 content:// 地址
thumbTempFilePath: string, // 视频为首帧缩略图路径(抽帧失败为空串);图片为空串,图片本身即封面
fileName: string,
fileExtension: string, // 'jpg' | 'mp4'
size: number, // 字节
mediaType: string, // 'image' | 'video'
width: number,
height: number,
duration: number, // 毫秒,图片恒为 0
resultType: string, // 'watermarked' | 'original'
usedTemplateId: string, // 实际生效的模板
usedFields: UTSJSONObject, // 最终烧录进照片的字段快照
// 以下 4 项是「拍这张那一刻」的状态快照,连拍途中改过设置也按张记录
showWatermark: boolean, // 拍这张时水印开关是否开;成对返回时原图与成片相同
cameraFacing: string, // 'back' | 'front';本地照片烧录链路为 ''
zoomRatio: number, // 拍摄时的实际变焦倍率;本地照片为 0
flashMode: string // 拍摄时设置的闪光模式 'off'|'on'|'auto'|'torch';本地照片为 ''
}
- 默认只返回最终成片;关闭「展示水印」拍摄时
resultType为'original'。 - 相机设置里启用「保留原图 / 保留原视频」后,
res.files按[原始文件, 水印成片]返回两项。 - 连拍确认输出时,
res.files按拍摄先后逐张展开为[原图1?, 成片1, 原图2?, 成片2, ...],res.file指向第一张成片。 - 最终实际烧录的字段以
usedFields为准。 - 本地照片烧录链路没有相机:结果项的
cameraFacing/flashMode为空串、zoomRatio为 0,showWatermark按是否实际烧录水印给出,captureMode恒为'photo'。
外部烧录(不开相机)
三个 API 与相机链路共用同一份渲染器与模板系统:同一份 watermarkData,相机拍、静默烧、贴片合成三处出图一致。全部不开相机页面,也不需要相机权限。
burnWatermark —— 静默烧录
给已有图片直接烧水印,无 UI。适合:文件管理里批量补烧、服务端存图前 App 端加工。
import { burnWatermark } from '@/uni_modules/zoo-watermark-camera'
burnWatermark({
filePaths: ['/storage/.../a.jpg', 'https://your.host/b.jpg'], // filePath 单张 或 filePaths 批量,远程图会先下载
watermarkData: { watermarkId: 1, title: '3 楼外机清洗', /* 同 openWatermarkCamera */ },
watermark: { scale: 0.9, opacity: 0.72, position: 'lb' },
quality: 92, // JPEG 质量 [50,100],默认 92;源图 PNG 输出保持 PNG
onProgress: (p) => console.log(p.index + 1 + '/' + p.total),
success: (res) => {
res.files // 成功成片数组(结构同 res.file),按入参顺序
res.failures // 批量逐张容错:[{ index, code, message }],全部成功时为空
},
fail: (err) => {}
})
- 批量逐张容错不中断;全部失败按第一张的错误 fail。
location入参与相机方案B 一致;缺省时插件用跨会话定位缓存(上次成功定位的地址),再没有就烧空值,不阻塞烧录。- 结果项
resultType恒为'watermarked',showWatermark恒为true;其余字段同相机成片。
editWatermarkPhoto —— 独立打开编辑器
打开水印编辑器编辑一张已有照片:可改字段/版式/位置,确认后烧录返回。返回契约与 openWatermarkCamera 完全一致。
import { editWatermarkPhoto } from '@/uni_modules/zoo-watermark-camera'
editWatermarkPhoto({
filePath: '/storage/.../a.jpg', // 必填;file:// 或沙盒绝对路径(不支持 http(s),远程图请先下载)
watermarkData: { watermarkId: 1, /* ... */ },
watermark: { scale: 0.9, position: 'lb' },
success: (res) => { /* res.file / res.files,同相机返回 */ },
fail: (err) => {}
})
generateWatermarkOverlay —— 透明底水印贴片
输出一张透明底 PNG 水印贴片,给插件原生能力覆盖不到的环境合成用:后台管理系统文件库(浏览器 canvas 合成)、小程序/H5、服务端批量合成。
import { generateWatermarkOverlay } from '@/uni_modules/zoo-watermark-camera'
generateWatermarkOverlay({
watermarkData: { watermarkId: 1, /* ... */ },
watermark: { scale: 0.9, opacity: 0.72 },
success: (res) => {
res.overlay.tempFilePath // 透明底 PNG 沙盒路径
res.overlay.suggestedWidthRatio // 建议:贴片宽 = 照片短边 × 该值(与原生烧录缩放公式一致)
res.overlay.suggestedPosition // 建议锚点,回显入参 position
},
fail: (err) => {}
})
浏览器端合成公式(与原生烧录同源):贴片宽 = 照片短边 × suggestedWidthRatio,边距 = 照片短边 × 0.025,按 suggestedPosition 锚点落位。仓库 tests/web-overlay-composite.html 是现成的合成验收页,可直接当后台文件库编辑页的合成参考实现。
编辑器偏好记忆
水印编辑器(editWatermarkPhoto 与相机拍完后的编辑页)会记住用户上次的水印偏好,插件内原生本地持久化(Android SharedPreferences / iOS UserDefaults,存储名 zoo_watermark_editor_prefs),不依赖宿主传参、不依赖任何后端——任何宿主 App 接入即自动获得。
- 记什么:水印样式(模板 id)、大小档位、透明度档位、四角位置。
- 何时写:只在点「完成」保存成功时写入;取消、中途退出不更新记忆。「移除水印」(无水印)状态不进记忆。
- 何时读:下次打开编辑器时自动套用——记忆的模板在本次传入的
templates列表内就优先于defaultTemplateId选中它(不在列表则照常回落defaultTemplateId→ 列表第一个);大小/透明度/位置有记忆值就覆盖模板下发的默认style/placement,首次使用(无记忆)维持现状。 - 相机拍摄链路另有独立的设置持久化(模板/大小/透明度/位置/拖动坐标等,见「JSON 配置中心」一节的本地记忆说明),两条链路各自记忆、语义一致。
运行时定位 Provider
插件提供与地图厂商无关的数据型定位协议。相机先打开,只有“水印已开启、当前模板存在可见地点字段、地点尚未就绪”时才按配置请求定位;插件不认识业务接口、地图 SDK、Key 或 Token。Provider 只接收 reason/requestId/sessionId 三个字符串,不能接收由 UTS 传出的回调函数;宿主完成异步任务后必须按 requestId 调成功或失败 API 回传。
五个公开 API:
| API | 返回 | 用途 |
|---|---|---|
setWatermarkCameraLocationProvider(provider) |
void |
注册或以 null 清除外部 Provider;Provider 参数为 (reason, requestId, sessionId) |
resolveWatermarkCameraLocation(requestId, location) |
boolean |
回传统一定位对象;只有命中当前有效请求且数据有效时返回 true |
rejectWatermarkCameraLocation(requestId, message) |
boolean |
回传可读失败原因;过期、已关闭或不存在的请求返回 false |
updateWatermarkCameraLocation(sessionId, location) |
boolean |
宿主主动更新当前会话地点;会话不匹配返回 false |
getWatermarkCameraState() |
ZooWatermarkCameraState |
查询当前会话、水印、模板、定位状态和是否可拍摄 |
可直接复制的外部 Provider 接入:
import {
setWatermarkCameraLocationProvider,
resolveWatermarkCameraLocation,
rejectWatermarkCameraLocation
} from '@/uni_modules/zoo-watermark-camera'
setWatermarkCameraLocationProvider((reason, requestId, sessionId) => {
console.log('[watermark-location-provider] start', { reason, requestId, sessionId })
yourLocationService().then(response => {
// code/message/data 是宿主业务协议,必须由宿主先校验、解包并转换;插件只接收下节的统一对象。
if (response.code !== 0) throw new Error(response.message || '定位失败')
const location = normalizeYourLocation(response.data)
resolveWatermarkCameraLocation(requestId, location)
}).catch(error => {
rejectWatermarkCameraLocation(requestId, error?.message || '定位失败')
})
})
reason 可能为 open、retry、watermark-enabled、template-changed。同一会话同一时刻最多一个外部请求;新请求、关闭水印、切换到无地点模板或关闭相机都会让旧请求过期,迟到结果返回 false 且不会污染下一次会话。
主动推送前先查询会话:
import { getWatermarkCameraState, updateWatermarkCameraLocation } from '@/uni_modules/zoo-watermark-camera'
const state = getWatermarkCameraState()
if (state.isOpen) updateWatermarkCameraLocation(state.sessionId, cachedLocation)
关闭态固定返回空 sessionId、isOpen: false、canCapture: false。打开时状态结构为:
type ZooWatermarkCameraState = {
isOpen: boolean
sessionId: string
captureMode: string // photo | video | burst
showWatermark: boolean
selectedTemplateId: string
requiresLocation: boolean // 当前模板是否含可见地点字段,不因已定位成功而改变
locationStatus: string // idle | loading | success | error
canCapture: boolean
}
统一定位 JSON
type ZooWatermarkCameraLocation = {
longitude?: number
latitude?: number
coordinateSystem?: 'gcj02' | 'wgs84'
level?: string
province?: string
city?: string
district?: string
detail?: string
addressFull?: string
addressArea?: string
township?: string
street?: string
streetNumber?: string
placeName?: string
placeType?: string
placeDistance?: number
source?: string
gpsWgs84?: string
weather?: string
}
结构化示例(全部是占位数据):
{
"longitude": 120.123456,
"latitude": 30.123456,
"coordinateSystem": "gcj02",
"level": "building",
"province": "示例省",
"city": "示例市",
"district": "示例区",
"detail": "示例街道示例路88号示例大厦",
"addressFull": "示例省示例市示例区示例街道示例路88号示例大厦",
"addressArea": "示例省示例市示例区",
"township": "示例街道",
"street": "示例路",
"streetNumber": "88号",
"placeName": "示例大厦",
"placeType": "aoi",
"placeDistance": 0,
"source": "host-provider",
"gpsWgs84": "120.123456,30.123456",
"weather": "晴 28℃"
}
weather 是可选的天气文案(如「晴 28℃」),宿主逆地理时顺手带回即可,插件会用它刷新 valueType=weather 的水印字段。不传或传空串表示「这次没拿到天气」,插件保留原有天气值,不会把水印上已有的天气抹空,所以天气查询失败时直接省略该字段就行,不要传空串以外的占位文案。
gpsWgs84 是旧模板的兼容字段,名称不能作为坐标系事实;新接入必须显式提供 coordinateSystem。addressFull 与 addressArea 也是旧对象兼容字段,普通字符串地址仍等价于 detail/addressFull。新结构优先,缺失项才用旧字段兜底。坐标小数位不等于精度:不要用 toFixed(13/14) 补零伪造精度;调用第三方逆地理服务时按其约束截取参数,回传对象仍应保留设备提供的原始有效数值。
地点面板的「展示省 / 展示市 / 展示区 / 展示详细位置」分别控制 province/city/district/detail,双端会持久化选择,并统一影响预览、编辑与最终成片。手动地址无法可靠拆分时整串放入 detail/addressFull,插件不会猜行政区划。
location 配置与降级
{
"location": {
"provider": "external",
"autoRequest": true,
"allowSystemFallback": true,
"blockCaptureWhileLoading": true,
"requireLocationBeforeCapture": true,
"requestTimeoutMs": 15000,
"loadingText": "定位中..."
}
}
| 配置 | 默认值 | 说明 |
|---|---|---|
provider |
system |
external 调宿主 Provider;system 用设备系统定位;none 只保留手动输入 |
autoRequest |
false |
相机就绪、打开水印或切换地点模板且地点未就绪时自动请求 |
allowSystemFallback |
true |
外部 Provider 场景在地点面板显示“使用系统定位”兜底;不自动偷偷切换 |
blockCaptureWhileLoading |
true |
当前模板需要地点且定位中时禁用快门 |
requireLocationBeforeCapture |
false |
需要地点但仍无成功地址时是否持续禁用快门;旧接入默认不强制 |
requestTimeoutMs |
15000 |
外部请求超时,配置会夹在 3000..60000 毫秒 |
loadingText |
定位中... |
地点字段和快门附近的等待文案,空字符串回落默认值 |
photo / video / burst(照片 / 录像 / 连拍)共用同一定位快门门禁:定位阻塞时快门不响应,中央显示 loading;关闭按钮、模式、水印、模板和地点入口仍可操作。外部失败或超时会停止 loading;若 requireLocationBeforeCapture=true 且地点仍为空,快门继续禁用,用户可重试、点击“使用系统定位”兜底或手动输入。关闭水印或切到无地点模板会立即解除门禁。
系统定位不会依赖外部 Provider。provider=external && allowSystemFallback=true 时地点面板标题右侧显示“使用系统定位”;权限缺失、拒绝、系统服务关闭或逆地理失败时不关闭面板,仍允许手动输入。provider=system 时“重新定位当前地址”本身就是系统定位,不重复显示兜底按钮;provider=none 时两个定位入口都隐藏。
错误码
| errCode | 含义 |
|---|---|
| 9013001 | 当前页面无法打开水印相机 |
| 9013002 | 当前已有相机任务正在执行 |
| 9013003 | 用户取消了拍摄 |
| 9013004 | 未获取到有效的拍摄结果 |
| 9013005 | 相机权限被拒绝(含 Android 宿主未声明 CAMERA) |
| 9013006 | 宿主未声明相机用途描述(iOS 缺 NSCameraUsageDescription) |
| 9013007 | 拍摄结果落盘失败 |
| 9013008 | 相机初始化失败 |
| 9013009 | 图片处理失败 |
| 9013010 | 图片路径缺失或文件不存在(外部烧录/编辑器) |
| 9013011 | 远程图片下载失败(外部烧录传入 http(s) 地址时) |
| 9013012 | 图片解码失败(外部烧录/编辑器) |
JSON 配置中心
插件根目录提供 zoo-watermark-camera.config.example.json,是带说明字段的宿主配置模板。复制到宿主 src/config/zoo-watermark-camera.config.json 后,在 App 启动时调用:
import cameraConfig from '@/config/zoo-watermark-camera.config.json'
import { configureWatermarkCamera } from '@/uni_modules/zoo-watermark-camera'
configureWatermarkCamera(cameraConfig)
主要配置项:
| 配置 | 默认值 | 说明 |
|---|---|---|
debugLogEnabled |
true |
输出配置解析、相册通道、录像等关键节点日志,真机报错时复制 [zoo-watermark-camera] 日志 |
useZooMediaPicker |
— | 本地照片通道开关:true 使用已注册的 zoo-media-picker,false 直接用系统相册 |
localPhoto.zooMediaPickerMode |
'system' |
zoo-media-picker 的调用模式,推荐 'custom' |
localPhoto.nativeFallback |
true |
自定义相册失败时是否回落系统相册 |
defaults.themeColor |
'#1677FF' |
主题色初始值 |
defaults.showWatermark |
true |
是否默认展示水印 |
defaults.watermarkScale |
0.7 |
水印大小初始值,范围 0.5-2 |
defaults.watermarkOpacity |
0.65 |
水印透明度初始值,范围 0.25-1 |
defaults.watermarkPosition |
'lb' |
水印初始位置 |
defaults.cameraFacing |
'back' |
默认前后摄 |
defaults.zoomRatio |
1 |
默认倍率 |
defaults.flashMode |
'off' |
默认闪光灯:off/on/auto/torch |
defaults.burstMaxCount |
10 |
连拍暂存上限,范围 1-30 |
defaults.* 是首次初始值:用户在相机内手动调整后会保存到本地并优先使用;要让新的配置初始值重新生效,需清除应用数据或在相机设置中改回。
本地照片与 zoo-media-picker 适配器
本地照片默认走 Android/iOS 系统相册;宿主安装了 zoo-media-picker 时可换成它的自绘相册。UTS 插件相互调用必须静态 import 插件根目录(@/uni_modules/zoo-media-picker),不支持相对路径,也不能按配置在运行时动态加载,所以配置里只有开关、没有「插件路径」字段。
接入步骤:
- 在宿主项目
uni_modules/中导入 zoo-media-picker。 - 配置顶层
useZooMediaPicker设为true。 - App 启动时注册适配器。注意适配器只收「模式 + 请求号」两个数据参数:UTS 调 JS 时携带的函数实参经桥接后在 JS 侧不可调用(Android 实测报
success is not a function),所以选图结果必须调插件导出的resolveLocalPhotoResult/rejectLocalPhotoResult按请求号回传:
import cameraConfig from '@/config/zoo-watermark-camera.config.json'
import { configureWatermarkCamera, resolveLocalPhotoResult, rejectLocalPhotoResult } from '@/uni_modules/zoo-watermark-camera'
import { chooseMediaPro } from '@/uni_modules/zoo-media-picker'
configureWatermarkCamera(cameraConfig, (mode, requestId) => {
chooseMediaPro({ count: 1, mediaType: ['image'], openType: mode,
success: res => resolveLocalPhotoResult(requestId, { tempFilePath: res.tempFiles[0]?.tempFilePath || '' }),
fail: err => rejectLocalPhotoResult(requestId, err?.errMsg || '选择图片失败')
})
})
resolveLocalPhotoResult(requestId, file):回传选图成功结果;requestId必须原样使用,file只需提供非空tempFilePath。rejectLocalPhotoResult(requestId, message):回传选图失败或取消原因;未知、过期或已完成的请求号会被忽略。
开关开启但未注册适配器、或调用失败时,双端会先弹出配置警告,再自动回落系统相册,本次选择流程不中断。
注意:本地照片链仅支持单张图片(adapter 必须保持 count: 1、mediaType: ['image'])。选取本地视频后烧录输出 MP4 当前不支持;相机内直接录制的视频支持水印烧录。本地照片遵循相机页当时的「展示水印」开关:开启时生成水印 JPG,关闭时输出原图副本。
连拍模式
底部模式条第三项「连拍」进入,点回「照片」或「视频」退出。进入后:
- 左下角的「本地照片」换成连拍暂存入口(错位堆叠缩略卡 +
n/上限计数),点开可逐张删除。 - 快门上方出现「清空连拍」和「确认(n)」。清空需二次确认,临时文件一并删除;确认把全部照片按拍摄先后一次性回传并关闭相机。
- 暂存上限由
defaults.burstMaxCount控制(默认 10,范围 1-30),达到上限后快门停用。 - 暂存是本地文件:切模式或直接关闭相机都不丢弃。再次进入连拍时若有未处理暂存,会弹窗询问「继续连拍」或「清空照片」,同一次相机会话只问一次。
每张连拍照片按拍摄那一刻的水印状态独立处理:
| 拍这张时的水印状态 | 设置里「保留原图」 | 该张产出 |
|---|---|---|
| 已开启水印 | 开 | 2 个文件:原图 + 水印成片 |
| 已开启水印 | 关 | 1 个文件:水印成片 |
| 已关闭水印 | 开或关 | 1 个文件:成片本身就是原图,不再重复留一份 |
所以连拍途中可随时开关水印,res.files 按拍摄先后逐张展开,每项的 resultType 区分 original / watermarked,usedTemplateId 与 usedFields 记录该张实际生效的模板与字段,showWatermark / cameraFacing / zoomRatio / flashMode 记录拍这张那一刻的相机状态。
宿主权限声明
插件不内置任何敏感权限,需宿主 App 自行声明,插件运行时申请;缺失时优雅报错或降级,不会崩溃:
- Android 拍照:
manifest.json→ App 权限配置加入android.permission.CAMERA。未声明时调用直接返回9013005。 - Android 带声音录像:再声明
android.permission.RECORD_AUDIO;拒绝时自动录制静音视频。 - Android 定位:相机内「重新定位」需
ACCESS_FINE_LOCATION/ACCESS_COARSE_LOCATION;未声明或拒绝后仍可手动输入地址。 - Android Logo/头像:插件自身声明普通
INTERNET权限用于加载logoUrl/photographerAvatar,不触发运行时授权弹窗。 - iOS:
NSCameraUsageDescription(必需);带声音录像加NSMicrophoneUsageDescription;相机内重新定位加NSLocationWhenInUseUsageDescription;iOS 13 及以下使用原生相册兜底还需NSPhotoLibraryUsageDescription。
FAQ
Q:标准基座能运行吗?
不能。本插件是 UTS 原生插件,必须制作自定义基座或云打包。Android 的 CameraX 依赖随基座构建,升级插件后如果行为没变,先重打基座并递增 versionCode,必要时卸载手机上的旧 App 再装。
Q:改了 watermarkName 为什么版式没变?
版式只由 watermarkId 匹配。watermarkName 只是覆盖显示名称,传了 ID=1 就永远是「安装服务记录」的版式,改名不换样式。
Q:location 传字符串还是对象?
都行。字符串等价于只提供完整地址;新接入推荐传结构化对象(longitude / latitude / coordinateSystem 与分段地址),旧对象(gpsWgs84 / addressFull / addressArea)继续兼容。外置模板复用 GPS 存证版式(preset: 'project-4')等需要原始经纬度的场景,只有对象形式能满足。
Q:不传 location 会怎样?
地点位显示「未授权位置」。allowEditLocation: true 时用户可在相机内手动输入或用系统定位反向解析地址。
Q:「保留原图」开启后回调怎么解析?
看 res.files:[原始文件, 水印成片] 两项,每项 resultType 分别为 original / watermarked;res.file 始终指向最终成片,兼容单张老调用。
Q:本地照片能选视频来烧录吗? 不能。本地照片链只支持单张图片;要水印视频请在相机内直接录制,输出就是已烧录的 MP4。
Q:配置里改了 watermarkScale / watermarkOpacity 为什么不生效?
这两个是首次初始值。用户在相机内手动调过大小/透明度后,本地值优先。清除应用数据或在相机设置中改回所需数值即可恢复配置生效。
Q:Logo / 头像图片加载失败会怎样? 自动回退为该版式的默认图形,不影响拍摄,也不走 fail。
Q:连拍关了相机,暂存的照片还在吗? 在。暂存是本地文件,跨模式、跨会话保留;下次进连拍会询问「继续连拍」或「清空照片」。
Q:在相机里手动改过地址后,GPS 存证类水印的经纬度行怎么补?
0.6.2 起自动补:手动改地址保存的定位只含地址不含坐标,旧版会因「已有地址」跳过自动定位导致坐标行一直为空;现在选中模板含可见 GPS 坐标行(valueType: 'location' 且 key: 'gpsWgs84')而坐标为空时,打开相机/切换模板会照常发起一次自动定位把坐标补齐。注意这会用手动地址之外的真实定位覆盖整份地址,是预期行为(这类水印要的是真实坐标);不需要坐标的模板行为不变。
授权说明
本插件按 uni appid + 包名 粒度授权,一次购买绑定一个应用。更换 appid 或包名需重新购买。

收藏人数:
https://gitee.com/harveyzoo/dcloud-uts-zoo-watermark-camera
购买源码授权版(
试用
使用 HBuilder 导入示例项目
赞赏(0)
下载 47
赞赏 0
下载 12736424
赞赏 1959
赞赏
京公网安备:11010802035340号