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

在这里插入图片描述

📌 模块概述

编辑影片模块是MovieTracker应用中用于修改已有影片信息的功能。用户可以编辑影片的各种信息,如标题、导演、评分、描述等。编辑功能与添加影片功能类似,但需要预加载现有的影片数据,并在保存时进行更新而不是创建新记录。

该模块的主要功能包括:加载影片数据、编辑表单、数据验证、海报更新、保存更改等。通过Cordova框架与OpenHarmony原生能力的结合,实现了完整的影片编辑流程。

编辑影片需要处理数据的加载和更新,同时需要跟踪哪些字段被修改,以便只更新改变的字段。

🔗 完整流程

第一步:影片数据加载

当用户打开编辑影片页面时,首先需要从数据库中加载要编辑的影片数据。加载过程包括获取影片的基本信息、分类、标签、海报等。

加载完成后需要将数据填充到表单中,使用户能够看到当前的影片信息。同时需要记录原始数据,以便在用户取消编辑时恢复原始状态。

第二步:表单编辑与交互

用户在表单中修改影片信息。编辑过程中需要实时验证输入数据,提供及时的反馈。同时需要跟踪哪些字段被修改,以便在保存时只更新改变的字段。

编辑过程需要支持撤销操作,用户可以恢复之前的修改。同时需要提供保存和取消按钮,用户可以选择保存修改或放弃修改。

第三步:数据验证与保存

在保存修改前需要进行数据验证,确保修改后的数据仍然有效。验证失败时需要显示错误消息,告知用户具体的错误位置。

验证成功后将修改保存到数据库,并显示成功消息。保存完成后可以返回影片详情页面或影片列表页面。

🔧 Web代码实现

编辑影片HTML结构

<div id="edit-movie-page" class="page">
    <div class="page-header">
        <h2>编辑影片</h2>
    </div>
    
    <form id="edit-movie-form" class="movie-form">
        <div class="form-section">
            <h3>基本信息</h3>
            
            <div class="form-group">
                <label>影片标题 *</label>
                <input type="text" id="edit-movie-title" placeholder="请输入影片标题" class="form-input" required>
            </div>
            
            <div class="form-group">
                <label>导演 *</label>
                <input type="text" id="edit-movie-director" placeholder="请输入导演名称" class="form-input" required>
            </div>
            
            <div class="form-group">
                <label>演员</label>
                <input type="text" id="edit-movie-actors" placeholder="多个演员用逗号分隔" class="form-input">
            </div>
            
            <div class="form-group">
                <label>年份 *</label>
                <input type="number" id="edit-movie-year" placeholder="请输入年份" class="form-input" required>
            </div>
        </div>
        
        <div class="form-section">
            <h3>分类与标签</h3>
            
            <div class="form-group">
                <label>分类 *</label>
                <select id="edit-movie-category" class="form-select" required></select>
            </div>
            
            <div class="form-group">
                <label>标签</label>
                <div id="edit-movie-tags" class="tags-select"></div>
            </div>
        </div>
        
        <div class="form-section">
            <h3>评分与描述</h3>
            
            <div class="form-group">
                <label>评分</label>
                <input type="number" id="edit-movie-rating" placeholder="1-10" min="1" max="10" step="0.5" class="form-input">
            </div>
            
            <div class="form-group">
                <label>描述</label>
                <textarea id="edit-movie-description" placeholder="请输入影片描述" class="form-textarea"></textarea>
            </div>
        </div>
        
        <div class="form-section">
            <h3>海报与状态</h3>
            
            <div class="form-group">
                <label>海报</label>
                <input type="file" id="edit-movie-poster" accept="image/*" class="form-input">
                <div id="edit-poster-preview" class="poster-preview"></div>
            </div>
            
            <div class="form-group">
                <label>状态 *</label>
                <select id="edit-movie-status" class="form-select" required>
                    <option value="watchlist">想看</option>
                    <option value="watched">已看</option>
                </select>
            </div>
        </div>
        
        <div class="form-actions">
            <button type="submit" class="btn btn-primary">保存修改</button>
            <button type="button" class="btn btn-secondary" onclick="cancelEdit()">取消</button>
        </div>
    </form>
</div>

这个HTML结构与添加影片页面类似,但用于编辑现有影片。所有输入字段都有相应的ID前缀"edit-"以区分。

影片数据加载与编辑

let currentEditingMovieId = null;
let originalMovieData = null;

async function loadMovieForEdit(movieId) {
    try {
        currentEditingMovieId = movieId;
        const movie = await db.getMovie(movieId);
        
        if (!movie) {
            showError('影片不存在');
            return;
        }
        
        // 保存原始数据
        originalMovieData = JSON.parse(JSON.stringify(movie));
        
        // 加载分类
        const categories = await db.getAllCategories();
        const categorySelect = document.getElementById('edit-movie-category');
        categorySelect.innerHTML = '';
        categories.forEach(cat => {
            const option = document.createElement('option');
            option.value = cat.id;
            option.textContent = cat.name;
            categorySelect.appendChild(option);
        });
        
        // 加载标签
        const tags = await db.getAllTags();
        const tagsContainer = document.getElementById('edit-movie-tags');
        tagsContainer.innerHTML = '';
        tags.forEach(tag => {
            const label = document.createElement('label');
            label.className = 'tag-checkbox-label';
            const isSelected = movie.tags && movie.tags.includes(tag.id);
            label.innerHTML = `
                <input type="checkbox" class="tag-checkbox" value="${tag.id}" ${isSelected ? 'checked' : ''}>
                <span>${tag.name}</span>
            `;
            tagsContainer.appendChild(label);
        });
        
        // 填充表单数据
        document.getElementById('edit-movie-title').value = movie.title;
        document.getElementById('edit-movie-director').value = movie.director;
        document.getElementById('edit-movie-actors').value = movie.actors || '';
        document.getElementById('edit-movie-year').value = movie.year;
        document.getElementById('edit-movie-category').value = movie.categoryId;
        document.getElementById('edit-movie-rating').value = movie.rating || '';
        document.getElementById('edit-movie-description').value = movie.description || '';
        document.getElementById('edit-movie-status').value = movie.status;
        
        // 显示海报预览
        if (movie.poster) {
            const preview = document.getElementById('edit-poster-preview');
            preview.innerHTML = `<img src="${movie.poster}" alt="海报预览">`;
        }
        
        // 绑定表单提交事件
        document.getElementById('edit-movie-form').addEventListener('submit', handleEditMovieSubmit);
        
        // 绑定海报上传事件
        document.getElementById('edit-movie-poster').addEventListener('change', handleEditPosterUpload);
    } catch (error) {
        console.error('加载影片失败:', error);
        showError('加载影片失败');
    }
}

async function handleEditMovieSubmit(event) {
    event.preventDefault();
    
    // 验证表单
    const errors = validateEditMovieForm();
    if (errors.length > 0) {
        showError(errors.join('\n'));
        return;
    }
    
    try {
        const updatedMovie = {
            title: document.getElementById('edit-movie-title').value,
            director: document.getElementById('edit-movie-director').value,
            actors: document.getElementById('edit-movie-actors').value,
            year: parseInt(document.getElementById('edit-movie-year').value),
            categoryId: parseInt(document.getElementById('edit-movie-category').value),
            rating: parseFloat(document.getElementById('edit-movie-rating').value) || null,
            description: document.getElementById('edit-movie-description').value,
            status: document.getElementById('edit-movie-status').value,
            poster: document.getElementById('edit-movie-poster').dataset.url || originalMovieData.poster,
            tags: getSelectedEditTags(),
            updatedDate: new Date().toISOString()
        };
        
        await db.updateMovie(currentEditingMovieId, updatedMovie);
        showSuccess('影片已更新');
        
        // 返回影片详情页面
        setTimeout(() => {
            app.navigateTo('movie-detail', currentEditingMovieId);
        }, 1000);
    } catch (error) {
        console.error('更新影片失败:', error);
        showError('更新影片失败');
    }
}

function validateEditMovieForm() {
    const errors = [];
    
    const title = document.getElementById('edit-movie-title').value.trim();
    if (!title) {
        errors.push('影片标题不能为空');
    }
    
    const director = document.getElementById('edit-movie-director').value.trim();
    if (!director) {
        errors.push('导演不能为空');
    }
    
    const year = parseInt(document.getElementById('edit-movie-year').value);
    if (!year || year < 1900 || year > new Date().getFullYear() + 5) {
        errors.push('年份无效');
    }
    
    const rating = parseFloat(document.getElementById('edit-movie-rating').value);
    if (rating && (rating < 1 || rating > 10)) {
        errors.push('评分必须在1-10之间');
    }
    
    return errors;
}

function getSelectedEditTags() {
    const checkboxes = document.querySelectorAll('#edit-movie-tags .tag-checkbox:checked');
    return Array.from(checkboxes).map(cb => parseInt(cb.value));
}

function cancelEdit() {
    if (confirm('确定要放弃修改吗?')) {
        app.navigateTo('movie-detail', currentEditingMovieId);
    }
}

这个函数实现了影片编辑的完整流程。loadMovieForEdit()加载影片数据并填充表单,handleEditMovieSubmit()处理表单提交并保存修改。

海报更新处理

function handleEditPosterUpload(event) {
    const file = event.target.files[0];
    if (!file) return;
    
    if (!file.type.startsWith('image/')) {
        showError('请选择图片文件');
        return;
    }
    
    if (file.size > 5 * 1024 * 1024) {
        showError('图片大小不能超过5MB');
        return;
    }
    
    const reader = new FileReader();
    reader.onload = function(e) {
        const preview = document.getElementById('edit-poster-preview');
        preview.innerHTML = `<img src="${e.target.result}" alt="海报预览">`;
        document.getElementById('edit-movie-poster').dataset.url = e.target.result;
    };
    reader.readAsDataURL(file);
}

这个函数处理海报的更新和预览。

🔌 OpenHarmony原生代码

编辑影片插件

// EditMoviePlugin.ets
import { webview } from '@kit.ArkWeb';
import { common } from '@kit.AbilityKit';

export class EditMoviePlugin {
    private context: common.UIAbilityContext;
    
    constructor(context: common.UIAbilityContext) {
        this.context = context;
    }
    
    public registerEditMovie(controller: webview.WebviewController): void {
        controller.registerJavaScriptProxy({
            object: new EditMovieBridge(),
            name: 'editMovieNative',
            methodList: ['trackChanges', 'validateChanges']
        });
    }
}

这个OpenHarmony原生插件为编辑影片提供了变更跟踪和验证功能。

变更跟踪实现

export class EditMovieBridge {
    public trackChanges(originalJson: string, modifiedJson: string): string {
        try {
            const original = JSON.parse(originalJson);
            const modified = JSON.parse(modifiedJson);
            
            const changes: any = {};
            
            Object.keys(modified).forEach(key => {
                if (JSON.stringify(original[key]) !== JSON.stringify(modified[key])) {
                    changes[key] = {
                        old: original[key],
                        new: modified[key]
                    };
                }
            });
            
            return JSON.stringify({
                hasChanges: Object.keys(changes).length > 0,
                changes: changes,
                changedFields: Object.keys(changes)
            });
        } catch (error) {
            return JSON.stringify({
                error: error.message
            });
        }
    }
    
    public validateChanges(changesJson: string): string {
        try {
            const changes = JSON.parse(changesJson);
            const errors: string[] = [];
            
            if (changes.title && !changes.title.new) {
                errors.push('影片标题不能为空');
            }
            
            if (changes.director && !changes.director.new) {
                errors.push('导演不能为空');
            }
            
            if (changes.year && changes.year.new < 1900) {
                errors.push('年份无效');
            }
            
            return JSON.stringify({
                valid: errors.length === 0,
                errors: errors
            });
        } catch (error) {
            return JSON.stringify({
                valid: false,
                error: error.message
            });
        }
    }
}

这个类实现了变更跟踪和验证功能。trackChanges()比较原始数据和修改后的数据,返回所有变更。validateChanges()验证变更的有效性。

Web-Native通信

调用原生变更跟踪

async function trackAndValidateChanges() {
    try {
        const modifiedMovie = {
            title: document.getElementById('edit-movie-title').value,
            director: document.getElementById('edit-movie-director').value,
            year: parseInt(document.getElementById('edit-movie-year').value),
            rating: parseFloat(document.getElementById('edit-movie-rating').value)
        };
        
        if (window.editMovieNative) {
            const trackResult = window.editMovieNative.trackChanges(
                JSON.stringify(originalMovieData),
                JSON.stringify(modifiedMovie)
            );
            const result = JSON.parse(trackResult);
            
            if (result.hasChanges) {
                console.log('变更字段:', result.changedFields);
                
                const validateResult = window.editMovieNative.validateChanges(
                    JSON.stringify(result.changes)
                );
                const validation = JSON.parse(validateResult);
                
                if (!validation.valid) {
                    showError(validation.errors.join('\n'));
                    return false;
                }
            }
        }
        
        return true;
    } catch (error) {
        console.error('变更跟踪失败:', error);
        return false;
    }
}

这个函数展示了如何调用OpenHarmony原生的变更跟踪功能。在保存修改前进行变更跟踪和验证。

📝 总结

编辑影片模块展示了Cordova与OpenHarmony混合开发中的数据加载、修改跟踪和验证功能。通过Web层提供完整的编辑表单界面,同时利用OpenHarmony原生能力进行变更跟踪和验证。

在实现这个模块时,需要注意数据的加载和填充、变更的跟踪、以及用户体验的流畅性。通过合理的架构设计,可以构建出高效、易用的影片编辑功能。

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐