更新记录

1.0.0(2026-10-26)

首个版本:像素级水印绘制、三种布局模板(工程巡检信息栏/简洁时间地点/设备资产记录)、七类内置字段与自定义字段、样式与输出参数配置、EXIF 方向校正、防 OOM 采样解码、长文本折行兜底、相册保存、定位权限与缓存定位读取、时间坐标格式化工具、57 项核心逻辑自动化测试。


平台兼容性

uni-app

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

szhzy-sitecamera 工程巡检水印相机插件

面向工程施工、设备巡检、物业管理、外勤服务、现场验收场景的 Android 水印相机 UTS 插件。

业务系统只需传入业务数据(工单编号、巡检人员、设备编号等),即可对拍照或相册图片叠加像素级水印——水印真实绘制进导出图片的像素中,而非覆盖在预览页面上,分享、下载、二次流转后依然完整保留。

内置三种专业布局:工程巡检信息栏(多字段全量记录)、简洁时间地点水印(轻量遮挡)、设备资产记录水印(设备名突出 + 详情栏)。支持字段显示/隐藏、显示顺序、文字字号、文字颜色、背景透明度、位置与边距配置。


⚠️ 使用前必读

  1. 本插件仅支持 Android App 平台(面向传统 uni-app Vue3 项目开发与验证;uni-app x 端按兼容模式编写但未实测)。iOS、鸿蒙、Web、小程序均不支持。
  2. 水印真实性边界(重要):水印中的拍摄时间与经纬度取自设备本地时钟与系统定位缓存,不构成不可篡改证据、司法级取证或可信时间戳认证。如业务需要防篡改存证,请在服务端另行建设(哈希固化、三方时间戳等)。
  3. 定位为可选能力:插件读取系统最近缓存定位(不发起定位请求、不伪造坐标)。权限被拒绝、定位服务关闭或无缓存结果时返回明确错误码,由业务层决定降级文案(如显示「定位不可用」或隐藏坐标字段)。
  4. 真机运行必须制作自定义基座:UTS 插件不能在 HBuilderX 标准基座上直接运行(见下文环境要求)。
  5. 诚实的验证状态:水印布局与配置处理核心逻辑已经过 57 项自动化测试;图片像素级绘制、EXIF 方向处理、相册保存等平台链路按 Android 官方 API 标准模式实现,插件作者未在真机上完成全量回归(开发期无 Android 测试机),使用前请先用 Demo 工程在你的目标机型上实测。
  6. 付费版授权与打包规则(DCloud 市场机制):本插件为付费插件,由插件市场自动加密——普通授权版买家不可见插件源码,需要阅读或修改源码请购买源码授权版;加密插件仅支持云端传统打包,不支持离线打包与安心打包;试用版仅可用于打包自定义基座调试,不能用于正式发布;授权绑定 AppID 与包名,购买后更换任一信息需重新购买授权。

环境要求

项 要求
HBuilderX 3.99+(建议使用最新 4.x 版本)
项目类型 uni-app(Vue3,主要支持目标);uni-app x(兼容模式编写,未实测)
Android 最低版本 6.0(API 23)
权限 定位:FINE/COARSE LOCATION(运行时申请,插件内负责);相册保存:Android 9- 需 WRITE_EXTERNAL_STORAGE(插件内自动申请),Android 10+ 无需权限;相机:由宿主工程 manifest 管理
其他 无第三方 SDK 依赖、无付费地图或定位服务依赖、不访问网络

真机运行提示:导入插件后,请在 HBuilderX 中执行「运行 → 运行到手机或模拟器 → 制作自定义基座」,基座制作完成后选择「使用自定义基座运行」;正式打包请使用云打包。Android 编译环境配置见 DCloud 官方文档《uts插件Android运行配置》。

快速开始

1. 安装

通过 HBuilderX 导入插件到项目的 uni_modules 目录。插件自带的 utssdk/app-android/AndroidManifest.xml 已声明定位与存储权限,云打包时自动合并。

2. 拍照 / 选图 + 绘制水印 + 保存相册(完整流程)

import {
  processImage, saveToAlbum, getLocation,
  requestLocationPermission, formatCoordinate
} from '@/uni_modules/szhzy-sitecamera'

// ① 请求定位权限并读取缓存定位(可选步骤;也可由业务系统传入自有定位)
const loc = await requestLocationPermission()
let locationText = '' // 定位不可用时的降级文案,由业务层决定
if (loc.ok) {
  const pos = getLocation()
  if (pos.ok) {
    // N22.54321°, E113.95432°(5 位小数约 1.1 米精度)
    locationText = formatCoordinate(pos.latitude, pos.longitude)
  } else {
    locationText = '定位不可用'
  }
} else {
  locationText = '定位未授权'
}

// ② 拍照或从相册选择(系统拍照能力,sourceType 控制来源)
uni.chooseImage({
  count: 1,
  sizeType: ['compressed'],
  sourceType: ['camera', 'album'], // camera 拍照 / album 相册
  success: async (res) => {
    // ③ 传入业务数据,绘制像素级水印
    const result = await processImage(res.tempFilePaths[0], {
      template: 'inspection', // 工程巡检信息栏布局
      fields: [
        { key: 'projectName', label: '项目', value: '科技园二期机电安装工程' },
        { key: 'serialNo',    label: '工单编号', value: 'GD-20261011-0012' },
        { key: 'personName',  label: '巡检人', value: '张三' },
        { key: 'deviceName',  label: '设备', value: '3F配电柜A-07' },
        { key: 'time',        label: '时间', value: '' },   // 留空 + time.auto 自动取当前时间
        { key: 'location',    label: '坐标', value: locationText },
        { key: 'remark',      label: '备注', value: '巡检正常,无异常发热' }
      ],
      style: {
        fontSize: 30,            // 基准字号(1080px 宽度基准,自动等比缩放)
        textColor: '#FFFFFF',
        backgroundColor: '#000000',
        backgroundOpacity: 0.55, // 背景透明度 0.1-0.95
        position: 'bottom',      // 信息栏位置 bottom / top
        marginX: 24, marginY: 24 // 边距(自动等比缩放)
      },
      image: { maxSide: 1920, quality: 88 }, // 最长边限制(防大图 OOM)/ JPEG 质量
      time: { auto: true, format: 'yyyy-MM-dd HH:mm:ss' },
      emptyPolicy: 'hide'        // 空值字段隐藏(或 'placeholder' 显示「未提供」)
    })

    if (result.ok) {
      // ④ 水印已真实绘制进图片像素:path / width / height / size
      console.log(`输出 ${result.width}x${result.height},${result.size} 字节`)
      console.log(`文件:${result.path}`)

      // ⑤ 可选:保存到系统相册
      const saved = await saveToAlbum(result.path)
      if (saved.ok) {
        uni.showToast({ title: '已保存到相册', icon: 'none' })
      }
    } else {
      console.error(`[${result.code}] ${result.message}`)
    }
  }
})

API 参考

所有异步 API 返回 Promise<UTSJSONObject>,统一信封结构,不通过 reject 抛业务错误:

{ ok: boolean, code: string, message: string, ...业务字段 }

图片处理

API 说明 返回
processImage(source, config) 对图片绘制水印并输出新图(JPEG) { ok, code, message, path, width, height, size, warnings }
saveToAlbum(path) 保存图片到系统相册 DCIM 目录 { ok, code, message, albumPath }
validateWatermarkConfig(config) 校验水印配置(不处理图片,表单即时反馈) { ok, code, message, warnings, fieldCount }

processImage 的 source 支持:uni.chooseImage 返回的临时路径(_doc/...)、file:// 路径、content:// 路径、文件绝对路径。

处理管线:采样解码(maxSide 限制最长边,4K 大图自动降采样防 OOM)→ EXIF 方向校正(横竖屏 / 镜像翻转全部处理)→ Canvas 像素级绘制 → JPEG 输出到应用缓存目录。输出图片 path 为绝对路径,可直接用于上传(uni.uploadFile)或预览。

定位

API 说明 返回
checkLocationPermission() 检查定位权限状态(同步) { granted, needRequest, message }
requestLocationPermission() 申请定位运行时权限 { ok, code, message, granted, doNotAskAgain? }
getLocation() 读取系统最近缓存定位(同步,不发起定位请求) { ok, code, message, latitude, longitude, accuracy, provider, time }
openLocationSettings() 跳转系统定位设置页 无

定位语义(如实):

  • getLocation 读取 GPS / Network / Passive 三通道中时间戳最新的缓存定位,即时返回、不耗电等待;
  • 定位服务关闭返回 LOCATION_SERVICE_OFF;无任何缓存返回 LOCATION_UNAVAILABLE——插件绝不伪造坐标,业务层按需展示降级文案或隐藏坐标字段;
  • 允许业务系统传入自有定位数据(如 uni.getLocation 结果、后端下发的作业点坐标)作为水印字段值——这不等于定位真实性认证,水印展示的坐标仅为信息记录;
  • 插件不做地理编码(坐标 → 地址)。如需地址展示,由业务系统提供地理编码接口并在业务层转换后传入。

格式化工具(纯逻辑,跨平台可用)

API 说明
formatTimestamp(ms, format) 毫秒时间戳 → 本地时间文本,占位符 yyyy MM dd HH mm ss,如 2026-10-11 13:41:23
formatCoordinate(lat, lng) 经纬度 → N22.54321°, E113.95432°(5 位小数,坐标越界返回空串)
formatLatitude(lat) / formatLongitude(lng) 单轴格式化(N/S、E/W 半球标记)
getPluginVersion() 插件版本号

错误码

code 含义 建议处理
SUCCESS 成功 -
INVALID_PARAM 参数缺失或非法 检查 source / config
INVALID_CONFIG 水印配置校验失败 查看 message 中的具体字段提示
FILE_NOT_FOUND 图片路径无法解析或文件不存在 检查 chooseImage 返回的路径传递
DECODE_FAILED 图片解码失败(文件损坏或格式不支持) 更换图片验证
SAVE_FAILED 水印绘制或文件输出失败 查看 message,多为磁盘空间不足
PERMISSION_DENIED 定位 / 存储权限被拒绝 引导授权或前往系统设置
LOCATION_SERVICE_OFF 系统定位服务未开启 引导开启(openLocationSettings)
LOCATION_UNAVAILABLE 无缓存定位结果 展示降级文案或由业务系统传入定位
NO_ACTIVITY 无法获取当前 Activity 环境异常,重启页面

水印配置详解

三种布局模板

inspection 工程巡检信息栏(默认)

底部(或顶部)全宽半透明信息栏,全部字段按传入顺序逐行展示,每行格式「标签:值」。适合巡检工单、施工记录等需要全量信息留痕的场景。

simple 简洁时间地点水印

小字号信息条,仅展示时间与坐标两类字段(其余字段自动忽略并给出警告)。适合不希望水印遮挡画面主体的日常记录场景。

asset 设备资产记录水印

顶部黑色标题条突出显示设备名称(1.3 倍字号),底部详情栏展示其余字段。适合设备点检、资产盘点等以设备为主体的场景。position 配置作用于底部详情栏位置,标题条恒在顶部。

字段体系

内置七类字段 key(label 不传时使用默认标签,也可完全自定义):

key 默认标签 说明
projectName 项目 项目名称
deviceName 设备 设备名称(asset 模板中作为标题条)
personName 人员 巡检 / 操作人员姓名
time 时间 拍摄时间;留空 + time.auto: true 自动取当前时间
location 坐标 经纬度文本,建议 formatCoordinate 生成
serialNo 编号 自定义业务编号(工单号、点检单号等)
remark 备注 备注信息,长文本自动折行

也支持任意自定义 key(如 line 工序、team 班组),传 label 即可。

字段控制:

  • 显示 / 隐藏:不传入该字段即隐藏;value 为空时按 emptyPolicy 处理(hide 隐藏 / placeholder 显示「未提供」);
  • 显示顺序:按 fields 数组顺序渲染;
  • 数值 value:自动转字符串(12 → "12",不出现 "12.0")。

样式与输出参数

字段 类型 默认 说明
style.fontSize number 30 基准字号,基于 1080px 宽度自动等比缩放(范围 12-72)
style.textColor string #FFFFFF 文字颜色,#RRGGBB / #AARRGGBB
style.backgroundColor string #000000 信息栏背景色,同上格式
style.backgroundOpacity number 0.55 背景透明度 0.1-0.95
style.position string bottom 信息栏位置 bottom / top
style.marginX / style.marginY number 24 信息栏边距(等比缩放,范围 0-200)
image.maxSide number 1920 输出图最长边像素(640-4096),超出自动降采样
image.quality number 88 JPEG 压缩质量(40-100)
time.auto boolean false time 字段缺值时自动取当前设备时间
time.format string yyyy-MM-dd HH:mm:ss 时间格式占位符

文本折行与兜底机制

长文本(如备注)按字符宽度估算自动折行(CJK 全角字符与 ASCII 区分宽度系数);绘制时用系统画笔实测每行宽度,估算偏差导致的超宽行自动整行缩放字号(下限 60%),仍超宽则截断加省略号——保证任何配置下水印都不溢出画面。

关于「水印真实性」的说明(请务必阅读)

  • 水印真实绘制进图片像素:导出的 JPEG 文件本身携带水印信息,转发、下载、脱离 App 查看时依然存在;
  • 但普通照片水印无法防止截图后裁剪、图像编辑软件篡改。本插件输出的水印照片不构成不可篡改证据、司法级取证材料或可信时间戳认证;
  • 水印中的时间取自设备本地时钟(可被用户修改系统时间影响),坐标取自系统缓存定位或业务系统传入值,两者均仅为信息记录;
  • 如业务需要防篡改能力:图片上传服务端后计算哈希固化、接入可信时间戳服务、或将原始图片与元数据分别存证——这些属于服务端方案,不在本插件范围内。

常见问题

Q:为什么拍照要用 uni.chooseImage,插件不内置相机界面? 首版遵循系统拍照边界(调用系统相机),保证兼容性与稳定性,不引入自定义相机预览框架的机型适配风险。sourceType: ['camera'] 即为拍照,体验与原生一致。视频水印不在本插件范围内。

Q:水印照片在部分手机相册里方向不对? 不会。插件已按 EXIF 方向标记对图片做旋转 / 镜像校正(覆盖 8 种 EXIF 方向值),输出图恒为视觉正立方向。

Q:处理 4K / 大图会崩溃吗? 采样解码机制保证内存可控:按 maxSide(默认 1920px)降采样后才解码像素,4K 拍照图的内存占用降低 4 倍以上;中间位图显式释放。对超大图(如 8000px 航拍图)建议把 maxSide 设为 1920-2560。

Q:连续快速处理多张图片会怎样? 每次处理独立执行(IO 线程 + 主线程回调),无共享可变状态;处理结果各自输出独立文件,不会相互覆盖。

Q:getLocation 返回 LOCATION_UNAVAILABLE? 设备从未有过任何定位记录(常见于刚重启的设备)。引导用户打开系统地图 App 定位一次、或直接开启定位服务后重试;也可由业务系统传入自有定位数据。

Q:保存到相册在 Android 9 及以下失败? 旧系统需要 WRITE_EXTERNAL_STORAGE 运行时权限,插件会自动弹窗申请;被拒绝后引导用户到系统设置开启(PERMISSION_DENIED 错误码)。Android 10+ 走 MediaStore,无需任何权限。

Q:支持 uni-app x 吗? 插件 API 统一以 UTSJSONObject 交互,代码按 uni-app 与 uni-app x 的兼容模式编写;但当前仅在传统 uni-app(Vue3)上完成设计与逻辑级验证,uni-app x 端未实测(市场平台声明中标注为不确定)。在 x 项目中使用请先自行评估。

技术支持

  • 使用问题与机型适配反馈请通过插件市场页面联系作者;
  • 反馈时请提供:手机型号、HBuilderX 版本、调用代码片段、code/message 与完整日志。

更新记录

见 changelog.md。授权条款见 license.md。

隐私、权限声明

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

ACCESS_FINE_LOCATION、ACCESS_COARSE_LOCATION(运行时申请,仅业务层调用定位功能时使用);WRITE_EXTERNAL_STORAGE(仅 Android 9 及以下保存相册时运行时申请,Android 10+ 走 MediaStore 无需权限)。拍照与选图由 uni.chooseImage 完成,相机权限由宿主工程 manifest 管理。

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

插件不采集、不上传任何数据。图片处理与水印绘制全部在设备本地完成;定位信息仅在业务层调用定位接口时于本地读取,是否上传由业务系统自行决定。水印中的时间与坐标仅为设备本地信息展示,不构成时间戳认证或定位真实性证明。

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

无

暂无用户评论。