欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。

在这里插入图片描述

📌 概述

新建日记模块是用户创建和编辑宠物日记的核心功能。这个模块提供了一个功能完整的编辑器,支持富文本编辑、图片上传、标签管理和宠物选择等功能。通过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![图片](${imagePath})\n`;
    editor.value += imageMarkdown;
}

这段代码展示了如何通过Cordova调用原生的相机和文件选择功能。通过Promise包装,我们能够以异步的方式获取图片路径,然后在编辑器中插入图片链接。

📝 总结

新建日记模块展示了Cordova与OpenHarmony在表单处理和多媒体操作方面的深度集成。在Web层,我们实现了复杂的表单验证、自动保存和富文本编辑。在原生层,我们提供了相机和文件选择等高级功能。

通过自动保存草稿机制,用户可以放心地编辑日记,不用担心内容丢失。通过Web-Native通信,我们能够充分利用OpenHarmony的多媒体能力,为用户提供更丰富的编辑功能。

在实际开发中,建议实现自动保存机制防止数据丢失,使用图片压缩优化存储空间,并提供实时预览帮助用户更好地组织内容。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐