更新记录
1.1.0(2026-08-20)
- iOS 新增 Swift 原生混编核心,使用 PhotoKit 读取真实相册,并通过 Vision 在本地检测人脸。
- Android 新增 Kotlin 原生混编核心,使用 MediaStore 读取真实相册,并通过随包 ML Kit Face Detection 在本地检测人脸。
- App 端活动、任务、候选和资源状态改由原生层保存;UTS 桥只传递基础标量和 JSON 字符串。
- 修复 iOS UTS
number生成NSNumber后直接传入 SwiftDouble参数导致插件整体编译失败的问题;原生层现显式接收并归一化NSNumber。 - 新增
pauseScanDirect、resumeScanDirect、cancelScanDirect、getScanStateDirect、getCandidatesDirect、prepareAssetsDirect、markAssetsProcessedDirect。 - demo 改为轮询原生任务状态,显示真实缩略图,不再注册跨桥复杂回调。
- 时间、地点和人脸筛选均在设备本地执行,不请求后台、不上传照片;不做人脸身份或儿童年龄判断。
平台兼容性
uni-app(5.24)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| - | - | - | - | - | - | - | - | - |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| - | - | - | - | - | - | - | - | - | - | - | - |
uni-app x(5.11)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| √ | √ | √ | √ | √ | √ |
i-photo-filter 照片智能筛选
当前版本定位
本 README 以已发布的 1.1.0 为基础,说明当前开发中、尚未发布的 UniAppX App 能力:在用户授权后真实读取手机相册,并在设备侧按活动时间、活动地点和人脸检测结果筛选照片。
插件不请求后台、不上传照片、不建立人脸库,也不保存人脸特征;筛选处理和缓存均在设备侧完成。真实系统相册扫描只在 iOS 和 Android App 提供;H5、微信小程序和 HarmonyOS 的兼容导出不提供真实自动相册扫描。
App 原生实现
- iOS:Swift 调用 PhotoKit 读取授权范围内的最近照片,使用 Vision 检测人脸。
- Android:Kotlin 调用 MediaStore 读取最近照片,使用随包 ML Kit Face Detection 检测人脸。
- 活动、任务、候选和资源状态保存在 Swift/Kotlin 原生层。
- App 的 UTS 桥只传递
string、number、boolean;任务状态、候选和资源结果通过原生 JSON 字符串返回。 - demo 每 400ms 轮询任务状态,不跨 iOS/Android 桥传递 Options、候选数组或 callback 对象。
- 候选缩略图写入应用缓存目录;只有用户确认候选后,
prepareAssetsDirect才按需准备原图临时文件。
平台能力
| 平台 | 权限与实现 | 当前能力 |
|---|---|---|
| iOS App | PhotoKit + Vision | 真实相册读取、时间/地点筛选、本地人脸检测、缩略图缓存、按需准备原图。 |
| Android App | MediaStore + ML Kit | 真实相册读取、时间/可用位置筛选、本地人脸检测、缩略图缓存、按需准备原图。 |
| H5 | unsupported |
不读取系统相册;兼容导出仅用于模拟和接口调试,不提供真实自动扫描。 |
| 微信小程序 | unsupported |
兼容导出不支持自动读取系统相册。 |
| HarmonyOS | unsupported |
兼容导出当前未接入原生相册实现,不提供真实自动扫描。 |
iOS 的 limited 权限只扫描用户授权的照片。照片没有位置数据、或 Android 未授予 ACCESS_MEDIA_LOCATION 时,locationMatched 与 distanceMeters 为 null:这表示位置未知。启用地点筛选时,locationMatched: null 不会直接使照片筛选失败,只有 locationMatched: false 表示实际地点不匹配并使地点条件失败。
ActivityProfile 示例
完整兼容画像仍使用 IPhotoFilterActivityProfile:
import { IPhotoFilterActivityProfile } from '@/uni_modules/i-photo-filter/interface.uts'
const profile : IPhotoFilterActivityProfile = {
activityId: 'children-sports-day-20260726',
name: '儿童夏日运动会',
startAt: 1785031200000,
endAt: 1785045600000,
timePaddingMinutes: 30,
location: {
latitude: 32.0603,
longitude: 118.7969,
radiusMeters: 1200
},
isChildrenOnly: true,
referenceImages: [
'/static/logo.png',
'/static/logo.png',
'/static/logo.png',
'/static/logo.png',
'/static/logo.png'
],
referenceTags: ['运动场', '彩色背心', '跑道', '儿童活动'],
highConfidenceThreshold: 78,
mediumConfidenceThreshold: 60,
profileVersion: 'demo-profile-1',
modelVersion: 'native-local-1'
}
referenceImages 是兼容契约字段,App 原生直连接口不会传输这些图片路径,不参与筛选。referenceTags 同样是兼容契约字段,不参与原生筛选。当前原生 MVP 只使用时间、地点、人脸及置信度阈值。
App API 使用示例
App 端使用 *Direct 接口。结构化返回值都是包含 ok、code、message、data 的 JSON 字符串:
import {
prepareActivityDirect,
startScanDirect,
getScanStateDirect,
getCandidatesDirect,
getScannedPhotosDirect,
prepareAssetsDirect,
markAssetsProcessedDirect
} from '@/uni_modules/i-photo-filter'
const activityId = 'children-sports-day-20260726'
const prepared = prepareActivityDirect(
activityId,
'儿童夏日运动会',
1785031200000,
1785045600000,
30,
32.0603,
118.7969,
1200,
true,
78,
60,
'demo-profile-1',
'native-local-1'
)
if (prepared) {
const taskId = startScanDirect(
activityId,
'album',
10,
true,
true,
true,
4,
120,
1,
20260726,
0
)
const stateJson = getScanStateDirect(taskId)
const candidatesJson = getCandidatesDirect(
activityId,
taskId,
'high,medium,low',
false,
1,
50
)
const scannedPhotosJson = getScannedPhotosDirect(taskId, 1, 50)
const assetIdsJson = '[]'
const assetsJson = prepareAssetsDirect(taskId, assetIdsJson)
const marked = markAssetsProcessedDirect(taskId, assetIdsJson)
console.log(stateJson, candidatesJson, scannedPhotosJson, assetsJson, marked)
}
业务页面应轮询 getScanStateDirect,在状态变为 completed、cancelled 或 failed 时停止轮询。
API 表
App 原生直连接口
| API | 返回值 | 行为 |
|---|---|---|
prepareActivityDirect(...) |
boolean |
用基础标量在原生层保存活动画像。 |
clearActivityCache(activityId) |
void |
清除活动及其原生任务缓存。 |
getPhotoPermissionStatus() |
PhotoPermissionStatus |
读取系统相册权限状态。 |
requestPhotoPermissionDirect() |
void |
请求系统相册权限。 |
startScanDirect(...) |
string |
启动真实相册扫描,返回 taskId;失败返回空字符串。 |
pauseScanDirect(taskId) |
string |
返回状态 JSON。 |
resumeScanDirect(taskId) |
string |
返回状态 JSON。 |
cancelScanDirect(taskId) |
string |
返回状态 JSON。 |
getScanStateDirect(taskId) |
string |
返回状态 JSON。 |
getCandidatesDirect(...) |
string |
返回分页候选 JSON,保留用于既有候选筛选流程。 |
getScannedPhotosDirect(taskId, page, pageSize) |
string |
返回当前任务的全部已枚举照片分页结果;包括命中、未命中及缩略图读取失败记录。iOS/Android 返回原生 JSON envelope。 |
prepareAssetsDirect(taskId, assetIdsJson) |
string |
返回已准备原图临时文件 JSON。 |
markAssetsProcessedDirect(taskId, assetIdsJson) |
boolean |
在原生任务中标记照片已处理。 |
兼容模拟接口
以下接口保留在 interface.uts 和非 App 共享实现中:prepareActivity、requestPhotoPermission、startScan、pauseScan、resumeScan、cancelScan、getScanState、getCandidates、prepareAssets、markAssetsProcessed、onScanProgress、offScanProgress、onCandidateBatch、offCandidateBatch、onScanStateChange、offScanStateChange、onAssetPrepareProgress、offAssetPrepareProgress、onPluginError、offPluginError。
App demo 不调用这些复杂 Options/Result/Event 接口,避免生成的 Swift/Kotlin 桥接复杂对象。
扫描照片分页结果
getScannedPhotosDirect(taskId, page, pageSize) 是新增的扫描结果查询接口,getCandidatesDirect(...) 仍完整保留以兼容已有调用。iOS/Android 成功时返回如下原生 envelope(list 中每项为 IPhotoFilterScannedPhoto):
{
"ok": true,
"code": 0,
"message": "",
"data": {
"taskId": "scan-20260727-001",
"total": 126,
"page": 1,
"pageSize": 50,
"list": []
}
}
扫描会保留任务中所有已枚举的照片记录,而不只保留候选。原生返回的 selected 是初始默认选择建议:filterMatched: true 且 confidenceLevel: "high" 的照片初始为选中。demo 的交互选择由 selectedAssetIds 独立维护,重新查询扫描结果不会回写或改变返回项的 photo.selected;可正常读取但未命中的照片可以手动选择,loadFailed: true 的照片不可选择。将 selectedAssetIds 中最终选中的 assetId 组成 JSON 数组传给 prepareAssetsDirect(taskId, assetIdsJson),即可按需准备原图临时文件。
候选字段表
| 字段 | 类型 | 当前含义 |
|---|---|---|
assetId |
string |
iOS PhotoKit local identifier 或 Android MediaStore ID。 |
source |
album \| mock |
候选来源。 |
thumbnailPath |
string |
应用缓存目录中的真实缩略图路径。 |
createdAt |
number |
照片拍摄时间,毫秒时间戳。 |
distanceMeters |
number \| null |
照片位置到活动地点的距离;无位置数据时为 null。 |
locationMatched |
boolean \| null |
是否在活动半径内;无位置数据时为 null。 |
faceCount |
number |
本地检测到的人脸数量。 |
personDetected |
boolean |
当前等价于 faceCount > 0。 |
personScore |
number |
当前基础人物分数。 |
childLikelihood |
number \| null |
原生 MVP 固定为 null,不做年龄判断。 |
subjectScore |
number |
当前基础主体分数。 |
sceneSimilarity |
number |
当前由时间命中提供的基础分数。 |
qualityScore |
number |
当前基础质量分数。 |
duplicateGroupId |
string |
预留重复照片分组字段。 |
totalScore |
number |
时间、地点和人脸基础分的汇总。 |
confidenceLevel |
high \| medium \| low |
根据活动阈值计算的等级。 |
reasonTags |
Array<string> |
time_matched、location_matched、face_detected。 |
selected |
boolean |
high 候选默认选中。 |
扫描照片字段表
IPhotoFilterScannedPhoto 用于 getScannedPhotosDirect 的 data.list。它与候选字段不同:它覆盖任务中全部已枚举照片,并明确表达筛选和读取状态。
| 字段 | 类型 | 含义 |
|---|---|---|
assetId |
string |
iOS PhotoKit local identifier 或 Android MediaStore ID,可作为 prepareAssetsDirect 的选中资源 ID。 |
source |
string |
照片来源;原生相册扫描为 album。 |
thumbnailPath |
string |
本地缩略图缓存路径;loadFailed: true 时为空字符串。 |
createdAt |
number |
照片拍摄时间,毫秒时间戳。 |
distanceMeters |
number \| null |
与活动地点的距离;无位置数据时为 null。 |
locationMatched |
boolean \| null |
true 为地点匹配,false 为实际不匹配,null 为位置未知;未知不会直接使已启用的地点筛选失败。 |
faceCount |
number |
本地检测到的人脸数量。 |
personDetected |
boolean |
当前等价于 faceCount > 0。 |
filterMatched |
boolean |
时间、地点、人脸等当前筛选条件均通过,且缩略图读取成功。 |
loadFailed |
boolean |
照片/缩略图读取失败,或缩略图缓存写入失败;失败记录仍保留在列表中,但不可选择。 |
confidenceLevel |
string |
按当前活动阈值得出的 high、medium 或 low 等级。 |
totalScore |
number |
当前时间、地点和人脸基础分的汇总。 |
reasonTags |
Array<string> |
当前匹配/失败原因标签,例如 time_matched、location_matched、face_detected 或 thumbnail_failed。 |
selected |
boolean |
原生扫描给出的初始默认选择建议;高置信度且命中的照片初始为 true。demo 的后续手动选择由独立的 selectedAssetIds 维护,不会回写此字段。 |
错误码表
| 错误码 | 常量 | 含义 |
|---|---|---|
9011001 |
PHOTO_FILTER_PERMISSION_DENIED |
照片访问权限被拒绝。 |
9011002 |
PHOTO_FILTER_PERMISSION_RESTRICTED |
当前平台限制照片访问。 |
9012001 |
PHOTO_FILTER_SCAN_NOT_FOUND |
未找到筛选任务。 |
9012002 |
PHOTO_FILTER_SCAN_BUSY |
筛选任务正在运行或任务不匹配。 |
9012003 |
PHOTO_FILTER_SCAN_STATE_INVALID |
当前任务状态不支持该操作。 |
9013001 |
PHOTO_FILTER_PROFILE_INVALID |
活动画像参数无效。 |
9013002 |
PHOTO_FILTER_MODEL_VERSION_INVALID |
模型版本无效。 |
9014001 |
PHOTO_FILTER_ASSET_NOT_FOUND |
未找到任务中的照片资源。 |
9014002 |
PHOTO_FILTER_ASSET_PREPARE_FAILED |
照片资源准备失败。 |
9015001 |
PHOTO_FILTER_CACHE_CLEAR_FAILED |
缓存或原生 JSON 处理失败。 |
PHOTO_FILTER_SCAN_NOT_FOUND(9012001)和 PHOTO_FILTER_SCAN_BUSY(9012002)均为公开常量。为兼容当前原生实现,iOS/Android 的 Direct JSON 方法在任务不存在或活动不匹配时返回 9012002;例如 getScannedPhotosDirect 的未知 taskId 不会返回 9012001。
隐私说明
- 筛选处理和缓存均在设备侧完成,不请求业务后台,也不向业务后台上传照片或人脸特征。
- 只在扫描时生成缩略图缓存;原图仅在用户确认后按需写入临时目录。
- 不做人脸身份比对,也不建立长期人脸库。
- iCloud 照片尚未下载到本机时,PhotoKit 可能通过系统管理的网络获取资源;插件本身不发起业务后台上传。
- “检测到人脸”不等于“确认是孩子”。当前
childLikelihood为null。 - 清除活动缓存会移除内存任务;系统缓存文件由应用缓存生命周期管理。
当前边界
- iCloud 中尚未下载到本机的照片可能需要 PhotoKit 临时下载,速度受系统和网络状态影响,但插件本身不连接业务后台。
- Android 7.0(API 24)及以上才读取系统 EXIF;Android 6.0(API 23)会跳过 EXIF 位置并按正常朝向处理,避免加载高版本系统类型。更高版本的位置元数据仍受照片 EXIF 和
ACCESS_MEDIA_LOCATION权限影响。 - 当前不做儿童年龄判断、身份识别、服装识别、参考图相似度、重复照片聚类或跨进程持久化断点。
- H5、微信小程序和 HarmonyOS 暂不提供真实自动相册扫描。
- 必须完成 iOS 与 Android 真机编译和运行验证后,才能把相应平台标记为已验收。

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