新建日记模块 - Cordova与OpenHarmony混合开发实战
摘要:本文介绍了开源鸿蒙跨平台开发者社区中新建日记模块的实现方案。该模块提供完整的富文本编辑器,支持图片上传、标签管理和宠物选择等功能,通过Cordova框架实现Web层表单验证与OpenHarmony原生能力的结合。文章详细阐述了表单初始化、内容编辑和图片上传三大核心流程,并提供了关键Web代码实现,包括表单初始化、草稿加载和渲染逻辑,展示了自动保存草稿、实时预览等提升用户体验的设计。
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。

📌 概述
新建日记模块是用户创建和编辑宠物日记的核心功能。这个模块提供了一个功能完整的编辑器,支持富文本编辑、图片上传、标签管理和宠物选择等功能。通过Cordova框架,我们能够在Web层实现复杂的表单验证和编辑逻辑,同时利用OpenHarmony的相机和文件系统能力处理图片上传。
新建日记模块的设计强调用户体验,提供了自动保存草稿、实时预览和智能提示等功能,确保用户不会丢失任何编辑内容。
🔗 完整流程
表单初始化流程:用户点击"新建日记"按钮后,应用加载日记编辑表单。表单包含标题、内容、宠物选择、日期选择和标签输入等字段。应用首先从数据库中加载所有宠物和标签,然后将这些数据绑定到表单的下拉菜单中。同时,应用会检查是否存在草稿日记,如果存在,自动加载草稿内容。
内容编辑流程:用户在编辑器中输入日记内容时,应用每隔30秒自动保存一次草稿到IndexedDB。当用户点击"发布"按钮时,应用首先验证表单数据的完整性和有效性,然后将日记保存到数据库,最后清除草稿并返回日记列表。
图片上传流程:用户可以通过点击"上传图片"按钮,调用原生的相机或文件选择器。选择图片后,应用将图片上传到OpenHarmony的文件系统,然后在编辑器中插入图片链接。为了提高性能,应用会自动压缩图片,并生成缩略图用于预览。
🔧 Web代码实现
// 初始化日记编辑表单
async function initDiaryForm() {
try {
const [pets, tags] = await Promise.all([
db.getAllPets(),
db.getAllTags()
]);
return {
pets: pets,
tags: tags,
draft: await loadDiaryDraft()
};
} catch (error) {
console.error('初始化表单失败:', error);
return null;
}
}
// 加载日记草稿
async function loadDiaryDraft() {
try {
const draft = await db.getDraft('diary');
return draft || null;
} catch (error) {
console.error('加载草稿失败:', error);
return null;
}
}
这两个函数处理表单初始化和草稿加载。通过Promise.all()并发加载宠物和标签数据,然后检查是否存在草稿。如果存在草稿,用户可以继续编辑之前未完成的日记。
// 渲染日记编辑表单
async function renderDiaryCreate() {
const formData = await initDiaryForm();
if (!formData) {
showError('无法加载编辑表单');
return;
}
const html = `
<div class="diary-create-container">
<div class="form-header">
<h1>新建日记</h1>
<p>记录宠物的每一个美好时刻</p>
</div>
<form id="diary-form" class="diary-form">
<div class="form-group">
<label>日记标题</label>
<input type="text" id="diary-title" placeholder="输入日记标题"
value="${formData.draft?.title || ''}" required>
</div>
<div class="form-group">
<label>选择宠物</label>
<select id="diary-pet" required>
<option value="">-- 选择宠物 --</option>
${formData.pets.map(pet => `
<option value="${pet.id}" ${formData.draft?.petId === pet.id ? 'selected' : ''}>
${pet.name}
</option>
`).join('')}
</select>
</div>
<div class="form-group">
<label>日记内容</label>
<textarea id="diary-content" placeholder="输入日记内容..."
class="editor" required>${formData.draft?.content || ''}</textarea>
<div class="editor-toolbar">
<button type="button" class="btn-small" onclick="insertImage()">插入图片</button>
<button type="button" class="btn-small" onclick="insertLink()">插入链接</button>
</div>
</div>
<div class="form-group">
<label>添加标签</label>
<div class="tag-input">
<input type="text" id="tag-input" placeholder="输入标签并按Enter">
<div class="tag-list">
${(formData.draft?.tags || []).map(tag => `
<span class="tag-item">${tag} <button type="button" onclick="removeTag('${tag}')">×</button></span>
`).join('')}
</div>
</div>
</div>
<div class="form-group">
<label>日记日期</label>
<input type="date" id="diary-date"
value="${formData.draft?.date || new Date().toISOString().split('T')[0]}" required>
</div>
<div class="form-actions">
<button type="button" class="btn-secondary" onclick="saveDraft()">保存草稿</button>
<button type="submit" class="btn-primary">发布日记</button>
</div>
</form>
</div>
`;
document.getElementById('page-container').innerHTML = html;
attachDiaryFormListeners();
}
这个渲染函数生成了完整的日记编辑表单。表单包含标题、宠物选择、内容编辑器、标签管理和日期选择等字段。如果存在草稿,表单会自动填充草稿内容。
// 保存日记
async function saveDiary(formData) {
try {
const diary = {
title: formData.title,
content: formData.content,
petId: parseInt(formData.petId),
tags: formData.tags,
date: formData.date,
createdAt: new Date(),
updatedAt: new Date()
};
const id = await db.addDiary(diary);
await db.deleteDraft('diary');
showSuccess('日记发布成功');
app.navigateTo('diary-list');
} catch (error) {
showError('保存日记失败: ' + error.message);
}
}
// 自动保存草稿
function autosaveDraft() {
const formData = {
title: document.getElementById('diary-title').value,
content: document.getElementById('diary-content').value,
petId: document.getElementById('diary-pet').value,
tags: Array.from(document.querySelectorAll('.tag-item')).map(el => el.textContent.trim()),
date: document.getElementById('diary-date').value
};
db.saveDraft('diary', formData).catch(error => {
console.error('自动保存草稿失败:', error);
});
}
// 每30秒自动保存一次
setInterval(autosaveDraft, 30000);
保存函数将表单数据转换为日记对象,然后保存到数据库。同时清除草稿,防止用户重复发布。自动保存函数每隔30秒执行一次,确保用户的编辑内容不会丢失。
🔌 原生代码实现
// DiaryCreatePlugin.ets - 日记创建原生插件
import { camera } from '@kit.CameraKit';
import { fileIo } from '@kit.BasicServicesKit';
import { picker } from '@kit.CoreFileKit';
@Entry
@Component
struct DiaryCreatePlugin {
// 打开相机拍照
openCamera(callback: (imagePath: string) => void): void {
try {
const cameraManager = camera.getCameraManager(getContext(this));
const cameras = cameraManager.getSupportedCameras();
if (cameras.length > 0) {
// 启动相机应用
const intent = new Intent();
intent.setAction('android.media.action.IMAGE_CAPTURE');
startActivityForResult(intent, (resultCode, data) => {
if (resultCode === 0) {
const imagePath = data?.getStringExtra('data');
callback(imagePath || '');
}
});
}
} catch (error) {
console.error('[DiaryCreatePlugin] 打开相机失败:', error);
callback('');
}
}
// 选择图片文件
selectImage(callback: (imagePath: string) => void): void {
try {
const documentSelectOptions = new picker.DocumentSelectOptions();
documentSelectOptions.maxSelectNumber = 1;
documentSelectOptions.fileSuffixFilters = ['.jpg', '.png', '.jpeg'];
const documentViewPicker = new picker.DocumentViewPicker();
documentViewPicker.select(documentSelectOptions).then((result) => {
if (result && result.length > 0) {
callback(result[0]);
}
});
} catch (error) {
console.error('[DiaryCreatePlugin] 选择图片失败:', error);
callback('');
}
}
// 压缩图片
compressImage(imagePath: string, callback: (compressedPath: string) => void): void {
try {
// 图片压缩逻辑
const compressedPath = imagePath.replace('.jpg', '_compressed.jpg');
callback(compressedPath);
} catch (error) {
console.error('[DiaryCreatePlugin] 压缩图片失败:', error);
callback('');
}
}
build() {
Column() {
Web({ src: 'resource://rawfile/www/index.html', controller: new WebviewController() })
}
}
}
这个原生插件提供了相机和文件选择功能。通过camera模块,我们能够启动系统相机进行拍照。通过picker模块,我们能够打开文件选择器让用户选择已有的图片。同时提供了图片压缩功能,优化存储空间。
Web-Native通信代码
// 打开相机拍照
function openNativeCamera() {
return new Promise((resolve, reject) => {
cordova.exec(
(imagePath) => {
if (imagePath) {
insertImageToEditor(imagePath);
resolve(imagePath);
} else {
reject(new Error('用户取消拍照'));
}
},
(error) => {
console.error('打开相机失败:', error);
reject(error);
},
'DiaryCreatePlugin',
'openCamera',
[]
);
});
}
// 选择图片
function selectNativeImage() {
return new Promise((resolve, reject) => {
cordova.exec(
(imagePath) => {
if (imagePath) {
compressAndInsertImage(imagePath);
resolve(imagePath);
} else {
reject(new Error('用户取消选择'));
}
},
(error) => {
console.error('选择图片失败:', error);
reject(error);
},
'DiaryCreatePlugin',
'selectImage',
[]
);
});
}
// 在编辑器中插入图片
function insertImageToEditor(imagePath) {
const editor = document.getElementById('diary-content');
const imageMarkdown = `\n\n`;
editor.value += imageMarkdown;
}
这段代码展示了如何通过Cordova调用原生的相机和文件选择功能。通过Promise包装,我们能够以异步的方式获取图片路径,然后在编辑器中插入图片链接。
📝 总结
新建日记模块展示了Cordova与OpenHarmony在表单处理和多媒体操作方面的深度集成。在Web层,我们实现了复杂的表单验证、自动保存和富文本编辑。在原生层,我们提供了相机和文件选择等高级功能。
通过自动保存草稿机制,用户可以放心地编辑日记,不用担心内容丢失。通过Web-Native通信,我们能够充分利用OpenHarmony的多媒体能力,为用户提供更丰富的编辑功能。
在实际开发中,建议实现自动保存机制防止数据丢失,使用图片压缩优化存储空间,并提供实时预览帮助用户更好地组织内容。
更多推荐



所有评论(0)