添加影片 - Cordova 与 OpenHarmony 混合开发实战
摘要:MovieTracker应用的添加影片模块提供了完整的影片信息录入功能,包括基本信息、分类标签、评分描述和海报上传。该模块采用表单分组设计,支持必填字段验证、多类型数据输入和图片上传处理。Web端实现包含HTML表单结构、数据初始化逻辑和表单提交处理,通过Cordova框架与OpenHarmony原生能力结合,为用户提供流畅的影片创建体验。
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。

📌 模块概述
添加影片模块是MovieTracker应用中用于创建新影片记录的功能。用户可以通过表单输入影片的各种信息,如标题、导演、演员、年份、评分、分类、标签等。添加影片是应用的核心功能,用户通过这个模块来构建自己的影片库。
该模块的主要功能包括:影片表单输入、数据验证、海报上传、分类和标签选择、影片状态设置等。通过Cordova框架与OpenHarmony原生能力的结合,实现了完整的影片创建流程和文件上传功能。
添加影片需要处理多种数据类型的输入,包括文本、日期、数字、文件等。同时需要进行数据验证,确保输入数据的有效性。
🔗 完整流程
第一步:表单设计与数据输入
添加影片页面需要提供一个完整的表单,包含影片的所有必要信息。表单需要进行分组,将相关的字段组织在一起,提高用户的输入效率。
表单设计需要考虑用户体验,提供清晰的标签、占位符文本、帮助提示等。同时需要支持必填字段的标记,告知用户哪些字段是必须填写的。
第二步:数据验证与错误处理
用户提交表单前需要进行数据验证。验证包括检查必填字段是否为空、检查数据格式是否正确、检查字段值是否在允许的范围内等。
验证失败时需要显示清晰的错误消息,告知用户具体的错误位置和原因,帮助用户快速修正错误。
第三步:海报上传与处理
用户可以为影片上传海报图片。上传过程需要处理文件选择、文件验证、文件上传等步骤。上传的图片需要进行处理,如缩放、压缩等,以节省存储空间。
上传过程需要显示进度提示,告知用户上传的进度。上传完成后需要显示上传的图片预览。
🔧 Web代码实现
添加影片HTML结构
<div id="add-movie-page" class="page">
<div class="page-header">
<h2>添加影片</h2>
</div>
<form id="add-movie-form" class="movie-form">
<div class="form-section">
<h3>基本信息</h3>
<div class="form-group">
<label>影片标题 *</label>
<input type="text" id="movie-title" placeholder="请输入影片标题" class="form-input" required>
</div>
<div class="form-group">
<label>导演 *</label>
<input type="text" id="movie-director" placeholder="请输入导演名称" class="form-input" required>
</div>
<div class="form-group">
<label>演员</label>
<input type="text" id="movie-actors" placeholder="多个演员用逗号分隔" class="form-input">
</div>
<div class="form-group">
<label>年份 *</label>
<input type="number" id="movie-year" placeholder="请输入年份" class="form-input" required>
</div>
</div>
<div class="form-section">
<h3>分类与标签</h3>
<div class="form-group">
<label>分类 *</label>
<select id="movie-category" class="form-select" required>
<option value="">请选择分类</option>
</select>
</div>
<div class="form-group">
<label>标签</label>
<div id="movie-tags" class="tags-select"></div>
</div>
</div>
<div class="form-section">
<h3>评分与描述</h3>
<div class="form-group">
<label>评分</label>
<input type="number" id="movie-rating" placeholder="1-10" min="1" max="10" step="0.5" class="form-input">
</div>
<div class="form-group">
<label>描述</label>
<textarea id="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="movie-poster" accept="image/*" class="form-input">
<div id="poster-preview" class="poster-preview"></div>
</div>
<div class="form-group">
<label>状态 *</label>
<select id="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="app.navigateTo('all-movies')">取消</button>
</div>
</form>
</div>
这个HTML结构定义了添加影片页面的布局。表单分为多个部分,包括基本信息、分类与标签、评分与描述、海报与状态。每个字段都有清晰的标签和占位符文本。
表单初始化与提交
async function initAddMovieForm() {
try {
// 加载分类
const categories = await db.getAllCategories();
const categorySelect = document.getElementById('movie-category');
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('movie-tags');
tags.forEach(tag => {
const label = document.createElement('label');
label.className = 'tag-checkbox-label';
label.innerHTML = `
<input type="checkbox" class="tag-checkbox" value="${tag.id}">
<span>${tag.name}</span>
`;
tagsContainer.appendChild(label);
});
// 绑定表单提交事件
document.getElementById('add-movie-form').addEventListener('submit', handleAddMovieSubmit);
// 绑定海报上传事件
document.getElementById('movie-poster').addEventListener('change', handlePosterUpload);
} catch (error) {
console.error('初始化表单失败:', error);
showError('初始化表单失败');
}
}
async function handleAddMovieSubmit(event) {
event.preventDefault();
// 验证表单
const errors = validateMovieForm();
if (errors.length > 0) {
showError(errors.join('\n'));
return;
}
try {
const movie = {
title: document.getElementById('movie-title').value,
director: document.getElementById('movie-director').value,
actors: document.getElementById('movie-actors').value,
year: parseInt(document.getElementById('movie-year').value),
categoryId: parseInt(document.getElementById('movie-category').value),
rating: parseFloat(document.getElementById('movie-rating').value) || null,
description: document.getElementById('movie-description').value,
status: document.getElementById('movie-status').value,
poster: document.getElementById('movie-poster').dataset.url || null,
tags: getSelectedTags(),
createdDate: new Date().toISOString()
};
await db.addMovie(movie);
showSuccess('影片已添加');
// 清空表单
document.getElementById('add-movie-form').reset();
// 返回影片列表
setTimeout(() => {
app.navigateTo('all-movies');
}, 1000);
} catch (error) {
console.error('添加影片失败:', error);
showError('添加影片失败');
}
}
function validateMovieForm() {
const errors = [];
const title = document.getElementById('movie-title').value.trim();
if (!title) {
errors.push('影片标题不能为空');
}
const director = document.getElementById('movie-director').value.trim();
if (!director) {
errors.push('导演不能为空');
}
const year = parseInt(document.getElementById('movie-year').value);
if (!year || year < 1900 || year > new Date().getFullYear() + 5) {
errors.push('年份无效');
}
const category = document.getElementById('movie-category').value;
if (!category) {
errors.push('请选择分类');
}
const rating = parseFloat(document.getElementById('movie-rating').value);
if (rating && (rating < 1 || rating > 10)) {
errors.push('评分必须在1-10之间');
}
return errors;
}
function getSelectedTags() {
const checkboxes = document.querySelectorAll('.tag-checkbox:checked');
return Array.from(checkboxes).map(cb => parseInt(cb.value));
}
这个函数实现了表单的初始化和提交处理。initAddMovieForm()加载分类和标签,绑定事件处理器。handleAddMovieSubmit()处理表单提交,进行数据验证和保存。validateMovieForm()验证表单数据的有效性。
海报上传处理
function handlePosterUpload(event) {
const file = event.target.files[0];
if (!file) return;
// 验证文件类型
if (!file.type.startsWith('image/')) {
showError('请选择图片文件');
return;
}
// 验证文件大小(限制为5MB)
if (file.size > 5 * 1024 * 1024) {
showError('图片大小不能超过5MB');
return;
}
// 读取文件并显示预览
const reader = new FileReader();
reader.onload = function(e) {
const preview = document.getElementById('poster-preview');
preview.innerHTML = `<img src="${e.target.result}" alt="海报预览">`;
// 保存文件URL
document.getElementById('movie-poster').dataset.url = e.target.result;
};
reader.readAsDataURL(file);
}
这个函数实现了海报上传和预览功能。验证文件类型和大小,然后读取文件并显示预览。
🔌 OpenHarmony原生代码
添加影片插件
// AddMoviePlugin.ets
import { webview } from '@kit.ArkWeb';
import { common } from '@kit.AbilityKit';
import { fileIo } from '@kit.CoreFileKit';
export class AddMoviePlugin {
private context: common.UIAbilityContext;
constructor(context: common.UIAbilityContext) {
this.context = context;
}
public registerAddMovie(controller: webview.WebviewController): void {
controller.registerJavaScriptProxy({
object: new AddMovieBridge(this.context),
name: 'addMovieNative',
methodList: ['uploadPoster', 'validateMovieData']
});
}
}
这个OpenHarmony原生插件为添加影片提供了海报上传和数据验证功能。
海报上传实现
export class AddMovieBridge {
private context: common.UIAbilityContext;
constructor(context: common.UIAbilityContext) {
this.context = context;
}
public uploadPoster(posterBase64: string, movieTitle: string): string {
try {
// 将Base64转换为文件
const buffer = this.base64ToBuffer(posterBase64);
// 生成文件名
const timestamp = Date.now();
const fileName = `poster_${movieTitle}_${timestamp}.jpg`;
// 保存到应用缓存目录
const cacheDir = this.context.cacheDir;
const filePath = `${cacheDir}/${fileName}`;
const file = fileIo.openSync(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE);
fileIo.writeSync(file.fd, buffer);
fileIo.closeSync(file.fd);
return JSON.stringify({
success: true,
filePath: filePath,
fileName: fileName
});
} catch (error) {
return JSON.stringify({
success: false,
error: error.message
});
}
}
public validateMovieData(movieJson: string): string {
try {
const movie = JSON.parse(movieJson);
const errors: string[] = [];
if (!movie.title || movie.title.trim().length === 0) {
errors.push('影片标题不能为空');
}
if (!movie.director || movie.director.trim().length === 0) {
errors.push('导演不能为空');
}
if (!movie.year || movie.year < 1900) {
errors.push('年份无效');
}
if (movie.rating && (movie.rating < 1 || movie.rating > 10)) {
errors.push('评分必须在1-10之间');
}
return JSON.stringify({
valid: errors.length === 0,
errors: errors
});
} catch (error) {
return JSON.stringify({
valid: false,
error: error.message
});
}
}
private base64ToBuffer(base64: string): ArrayBuffer {
const binaryString = atob(base64.split(',')[1]);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}
}
这个类实现了海报上传和数据验证功能。uploadPoster()将Base64编码的图片保存到文件系统,validateMovieData()验证影片数据的有效性。
Web-Native通信
调用原生上传功能
async function uploadPosterToNative(posterBase64) {
try {
const movieTitle = document.getElementById('movie-title').value;
if (window.addMovieNative) {
const uploadResult = window.addMovieNative.uploadPoster(
posterBase64,
movieTitle
);
const result = JSON.parse(uploadResult);
if (result.success) {
document.getElementById('movie-poster').dataset.url = result.filePath;
showSuccess('海报已上传');
} else {
showError(`上传失败: ${result.error}`);
}
}
} catch (error) {
console.error('上传海报失败:', error);
showError('上传海报失败');
}
}
async function validateBeforeSave() {
try {
const movie = {
title: document.getElementById('movie-title').value,
director: document.getElementById('movie-director').value,
year: parseInt(document.getElementById('movie-year').value),
rating: parseFloat(document.getElementById('movie-rating').value)
};
if (window.addMovieNative) {
const validationResult = window.addMovieNative.validateMovieData(
JSON.stringify(movie)
);
const result = JSON.parse(validationResult);
if (!result.valid) {
showError(result.errors.join('\n'));
return false;
}
}
return true;
} catch (error) {
console.error('验证失败:', error);
return false;
}
}
这个函数展示了如何调用OpenHarmony原生的上传和验证功能。在上传海报和保存影片前进行验证和上传处理。
📝 总结
添加影片模块展示了Cordova与OpenHarmony混合开发中的表单处理和文件上传功能。通过Web层提供完整的表单界面和用户交互,同时利用OpenHarmony原生能力进行文件上传和数据验证。
在实现这个模块时,需要注意表单数据的验证、文件上传的处理、以及用户体验的流畅性。通过合理的架构设计,可以构建出高效、易用的影片添加功能。
更多推荐
所有评论(0)