AtomicServiceEnhancedWeb:AtomicServiceEnhancedWeb是AtomicServiceWeb的增强版,为开发者提供Web页面加载、页面交互、更多H5高阶开放能力等,保障开发者在H5混合ArkTS开发的场景有更加连续的体验。
AtomicServiceEnhancedWeb JS SDK:为开发者提供满足定制化诉求的Web高阶组件,屏蔽ArkWeb组件中无需关注的接口,并提供JS扩展能力。
has.cameraPicker.pick:拍照或录像。
has.getLocalImgData:获取本地图片。
has.filePreview.openPreview:预览文件,只支持预览本地文件。
受限于Web组件的安全沙箱机制,H5页面无法直接读取系统公共目录(如系统相机生成的临时文件)。本方案利用ArkTS层拥有完整文件读写权限的特性,将ArkTS作为数据中转桥梁,将系统临时文件迁移至Web组件可访问的私有沙箱目录(CacheDir),从而实现H5端的文件读取与预览。
实现逻辑:
前置准备:
完整代码如下:
import { AtomicServiceEnhancedWeb, AtomicServiceEnhancedWebController } from '@atomicservice/ascfapi';
import common from '@ohos.app.ability.common';
import fs from '@ohos.file.fs';
@Entry
@Component
struct Index {
@State webUrl: Resource = $rawfile('camera.html');
@State asController: AtomicServiceEnhancedWebController = new AtomicServiceEnhancedWebController();
context = this.getUIContext().getHostContext() as common.UIAbilityContext;
build() {
Navigation() {
Stack({ alignContent: Alignment.Bottom }) {
AtomicServiceEnhancedWeb({
src: this.webUrl,
controller: this.asController,
onTitleReceive: (event) => {
// 协议格式:URI:file://.....
if (event && event.title && event.title.startsWith('URI:')) {
const originalUri = event.title.substring(4);
this.moveFileToSandbox(originalUri);
}
}
})
.width('100%')
.height('100%')
}
}
.title('元服务 H5 相机调用')
.titleMode(NavigationTitleMode.Mini)
.hideBackButton(true)
}
// 搬运图片和视频
async moveFileToSandbox(srcUri: string) {
try {
console.info('[ArkTS] 收到文件,准备搬运: ' + srcUri);
// 1. 自动识别文件后缀 (jpg 还是 mp4)
// srcUri 可能是 file://.../IMG_2024.jpg
let extension = 'dat'; // 默认后缀
const dotIndex = srcUri.lastIndexOf('.');
if (dotIndex > 0) {
extension = srcUri.substring(dotIndex + 1);
}
// 2. 构造沙箱路径:/data/storage/.../cache/temp_file.mp4
// 使用时间戳防止覆盖
const fileName = `temp_${new Date().getTime()}.${extension}`;
const sandboxPath = this.context.cacheDir + '/' + fileName;
// 3. 复制文件
const srcFile = await fs.open(srcUri, fs.OpenMode.READ_ONLY);
const destFile = await fs.open(sandboxPath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE | fs.OpenMode.TRUNC);
const bufSize = 4096;
let buffer = new ArrayBuffer(bufSize);
let off = 0;
let stat = await fs.stat(srcFile.fd);
let total = stat.size;
while (off < total) {
let readLen = await fs.read(srcFile.fd, buffer, { offset: off });
await fs.write(destFile.fd, buffer, { length: readLen });
off += readLen;
}
await fs.close(srcFile.fd);
await fs.close(destFile.fd);
console.info('[ArkTS] 搬运完成: ' + sandboxPath);
// 4. 通知 H5:文件好了,路径给你,类型也给你
// 回调参数: (路径, 文件类型: image/video)
const fileType = (extension === 'mp4' || extension === 'mov') ? 'video' : 'image';
this.asController.loadUrl(`javascript:window.onSandboxFileReady('${sandboxPath}', '${fileType}')`);
} catch (err) {
console.error('[ArkTS] 搬运失败: ' + JSON.stringify(err));
this.asController.loadUrl(`javascript:showToast('文件处理失败')`);
}
}
} <!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>元服务 H5 相机调用</title>
<script src="./aseweb-sdk.umd.js"></script>
<style>
body {
background: #fff;
text-align: center;
padding-top: 20px;
font-family: sans-serif;
}
/* 预览区 */
#preview-box {
width: 90%;
height: 40vh;
background: #f5f5f5;
margin: 0 auto;
border-radius: 12px;
border: 1px solid #eee;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
#preview-img {
max-width: 100%;
max-height: 100%;
display: none;
}
.placeholder {
color: #999;
font-size: 14px;
}
/* 按钮组 */
.btn-group {
display: flex;
justify-content: space-around;
margin-top: 30px;
padding: 0 20px;
}
.btn {
width: 45%;
padding: 14px;
color: white;
border: none;
border-radius: 25px;
font-size: 16px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.btn-photo {
background: #007DFF;
}
.btn-video {
background: #FA2A2D;
}
#toast {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.8);
color: #fff;
padding: 12px 24px;
border-radius: 8px;
display: none;
z-index: 99;
}
</style>
</head>
<body>
<div id="toast"></div>
<div id="preview-box">
<span id="tips" class="placeholder">点击下方按钮开始</span>
<img id="preview-img">
</div>
<div class="btn-group">
<button class="btn btn-photo" onclick="startCamera('image')"> 拍照</button>
<button class="btn btn-video" onclick="startCamera('video')"> 录像</button>
</div>
<p style="color:#999; font-size:12px; margin-top:15px;">照片直接预览,视频将调起系统预览</p>
<script>
function showToast(msg) {
const el = document.getElementById('toast');
el.innerText = msg; el.style.display = 'block';
setTimeout(() => el.style.display = 'none', 2000);
}
// 1. 启动相机 (支持 image 或 video)
function startCamera(type) {
if (typeof has === 'undefined' || !has.cameraPicker) {
alert("SDK未就绪"); return;
}
// 这里的 type 传入 'image' 或 'video'
has.cameraPicker.pick({
mediaTypes: [type],
cameraPosition: 0,
saveUri: '',
videoDuration: 30, // 录像限制时长(秒)
callback: (err, res) => {
if (err) {
if (err.code !== 1000600001) {
showToast("错误:" + JSON.stringify(err));
}
return;
}
if (res && res.resultUri) {
showToast("正在处理文件...");
// 传给 ArkTS 搬运
document.title = "URI:" + res.resultUri;
setTimeout(() => { document.title = "元服务相机"; }, 500);
}
}
});
}
// 2. ArkTS 搬运完成回调
// sandboxPath: 沙箱内的绝对路径
// fileType: 'image' 或 'video'
window.onSandboxFileReady = function (sandboxPath, fileType) {
console.log("沙箱文件就绪:", sandboxPath, fileType);
if (fileType === 'image') {
// --- 图片逻辑:使用 getLocalImgData 内嵌预览 ---
handleImagePreview(sandboxPath);
} else {
// --- 视频逻辑:使用 filePreview 打开全屏预览 ---
handleVideoPreview(sandboxPath);
}
}
// 处理图片预览
function handleImagePreview(path) {
if (!has.getLocalImgData) { showToast("预览接口不可用"); return; }
has.getLocalImgData({
uri: path,
success: (res) => {
if (res.localData) {
let src = res.localData;
if (!src.startsWith('data:image')) {
src = 'data:image/jpeg;base64,' + src;
}
document.getElementById('preview-img').src = src;
document.getElementById('preview-img').style.display = 'block';
document.getElementById('tips').style.display = 'none';
showToast(" 图片预览成功");
}
}
});
}
// 处理视频预览 (使用系统预览能力)
function handleVideoPreview(path) {
if (!has.filePreview || !has.filePreview.openPreview) {
alert("当前系统版本不支持 filePreview 接口");
return;
}
// 调用系统预览
has.filePreview.openPreview({
uri: path, // 传入沙箱路径
title: '录像回放', // 预览窗口标题
type: 'video/mp4', // 显式指定 MIME 类型,帮助系统识别
success: () => {
console.log("预览拉起成功");
},
fail: (err) => {
showToast("预览失败: " + JSON.stringify(err));
}
});
}
</script>
</body>
</html>