活动记录 Cordova 与 OpenHarmony 混合开发实战
本文介绍了活动记录功能的实现方案,包含Web和OpenHarmony原生开发两个版本。该功能记录用户操作(创建、编辑、删除等),并按时间排序展示历史记录。Web端使用JavaScript实现数据存储和渲染界面,OpenHarmony端通过TypeScript开发原生插件,利用文件系统存储活动数据。两者都实现了记录、存储和显示三个核心步骤,展示了混合开发中的日志系统实现方式。

📌 模块概述
活动记录功能为用户提供全维度的操作追踪能力,在基础操作记录的基础上,新增操作类型分类、时间筛选、记录搜索、批量管理、操作溯源等实用功能,帮助用户清晰追溯笔记的创建、编辑、删除等所有操作行为,同时支持记录的筛选和清理,提升操作日志的实用性。
🔗 完整流程
第一步:精细化记录活动
记录操作类型(创建 / 编辑 / 删除 / 导入 / 导出 / 同步等)、操作对象、操作结果;
补充设备信息、操作 IP(可选)等溯源数据,完善记录维度。
第二步:结构化存储活动
按时间分片存储记录,避免单文件过大;
支持记录的持久化存储,可配置保留时长。
第三步:多维度展示与管理
按时间 / 类型筛选记录,支持关键词搜索;
展示操作记录详情,支持批量删除 / 清空记录;
对敏感操作(如删除)进行醒目标识
🔧 Web代码实现
// 活动记录页面(扩展版)
async renderActivity() {
const activities = await noteDB.getAllActivities();
// 按时间降序排序
const sortedActivities = activities.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
// 提取所有操作类型用于筛选
const activityTypes = [...new Set(sortedActivities.map(act => act.type))];
return `
<div class="page active">
<div class="page-header">
<h1 class="page-title">📅 活动记录</h1>
</div>
<!-- 新增:筛选和搜索功能 -->
<div class="activity-filter" style="margin: 16px 0;">
<select id="activity-type-filter" class="form-control" onchange="app.filterActivities()">
<option value="all">所有操作类型</option>
${activityTypes.map(type => `<option value="${type}">${type}</option>`).join('')}
</select>
<input type="search" id="activity-search" class="form-control"
placeholder="搜索操作描述..." style="margin-top: 8px;"
oninput="app.filterActivities()">
<div class="activity-actions" style="margin-top: 8px; display: flex; gap: 8px;">
<button class="btn btn-outline" onclick="app.clearAllActivities()">清空记录</button>
<button class="btn btn-outline" onclick="app.exportActivities()">导出记录</button>
</div>
</div>
<!-- 活动记录列表 -->
<div class="activity-list" id="activity-list">
${sortedActivities.map(activity => {
// 新增:为不同操作类型添加样式标识
const typeClass = {
'删除': 'activity-delete',
'创建': 'activity-create',
'编辑': 'activity-edit',
'导入': 'activity-import',
'导出': 'activity-export',
'同步': 'activity-sync'
}[activity.type] || 'activity-default';
return `
<div class="activity-item ${typeClass}">
<div class="activity-header">
<span class="activity-type">${activity.type}</span>
<span class="activity-time">${Utils.formatDate(activity.createdAt)}</span>
<!-- 新增:设备信息展示 -->
<span class="activity-device">${activity.device || '未知设备'}</span>
</div>
<div class="activity-description">${activity.description}</div>
<!-- 新增:操作ID和快捷操作 -->
<div class="activity-footer">
<span class="activity-id">ID: ${activity.id}</span>
<button class="btn btn-text" onclick="app.deleteSingleActivity(${activity.id})">删除</button>
</div>
</div>
`;
}).join('') || '<div class="empty-state">暂无活动记录</div>'}
</div>
</div>
`;
}
// 记录活动(扩展版)
async recordActivity(type, description, extra = {}) {
try {
// 新增:获取设备信息(调用原生能力)
const deviceInfo = await new Promise(resolve => {
cordova.exec(resolve, () => resolve('未知设备'), 'activityPlugin', 'getDeviceInfo', []);
});
const activity = {
id: Date.now(),
type: type,
description: description,
createdAt: new Date().toISOString(),
device: deviceInfo,
// 新增:支持扩展字段
...extra
};
await noteDB.addActivity(activity);
// 可选:超过1000条记录时自动清理最早的记录
await this.cleanOldActivities(1000);
} catch (error) {
console.error('记录活动失败:', error);
}
}
// 新增:筛选活动记录
async filterActivities() {
try {
const typeFilter = document.getElementById('activity-type-filter').value;
const searchText = document.getElementById('activity-search').value.toLowerCase();
const activities = await noteDB.getAllActivities();
let filtered = activities;
// 按类型筛选
if (typeFilter !== 'all') {
filtered = filtered.filter(act => act.type === typeFilter);
}
// 按关键词搜索
if (searchText) {
filtered = filtered.filter(act =>
act.description.toLowerCase().includes(searchText) ||
act.type.toLowerCase().includes(searchText)
);
}
// 按时间降序排序
filtered.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
// 更新列表DOM
const listContainer = document.getElementById('activity-list');
listContainer.innerHTML = filtered.map(activity => {
const typeClass = {
'删除': 'activity-delete',
'创建': 'activity-create',
'编辑': 'activity-edit'
}[activity.type] || 'activity-default';
return `
<div class="activity-item ${typeClass}">
<div class="activity-header">
<span class="activity-type">${activity.type}</span>
<span class="activity-time">${Utils.formatDate(activity.createdAt)}</span>
<span class="activity-device">${activity.device || '未知设备'}</span>
</div>
<div class="activity-description">${activity.description}</div>
<div class="activity-footer">
<span class="activity-id">ID: ${activity.id}</span>
<button class="btn btn-text" onclick="app.deleteSingleActivity(${activity.id})">删除</button>
</div>
</div>
`;
}).join('') || '<div class="empty-state">暂无符合条件的记录</div>';
} catch (error) {
console.error('筛选活动记录失败:', error);
Utils.showToast('筛选失败,请重试', 'error');
}
}
// 新增:删除单条记录
async deleteSingleActivity(activityId) {
if (confirm('确定删除这条活动记录吗?')) {
try {
await noteDB.deleteActivity(activityId);
Utils.showToast('记录已删除', 'success');
await this.renderActivity(); // 刷新页面
} catch (error) {
console.error('删除记录失败:', error);
Utils.showToast('删除失败,请重试', 'error');
}
}
}
// 新增:清空所有记录
async clearAllActivities() {
if (confirm('确定清空所有活动记录吗?此操作不可恢复!')) {
try {
await noteDB.clearAllActivities();
Utils.showToast('所有记录已清空', 'success');
await this.renderActivity(); // 刷新页面
} catch (error) {
console.error('清空记录失败:', error);
Utils.showToast('清空失败,请重试', 'error');
}
}
}
// 新增:导出活动记录
async exportActivities() {
try {
const activities = await noteDB.getAllActivities();
const content = JSON.stringify(activities, null, 2);
const blob = new Blob([content], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `activity_logs_${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
Utils.showToast('活动记录导出成功', 'success');
} catch (error) {
console.error('导出记录失败:', error);
Utils.showToast('导出失败,请重试', 'error');
}
}
// 新增:清理旧记录(防止数据过多)
async cleanOldActivities(maxCount = 1000) {
try {
const activities = await noteDB.getAllActivities();
if (activities.length > maxCount) {
// 保留最新的maxCount条记录
const sorted = activities.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
const toDelete = sorted.slice(maxCount);
for (const act of toDelete) {
await noteDB.deleteActivity(act.id);
}
}
} catch (error) {
console.error('清理旧记录失败:', error);
}
}
// 挂载全局方法
window.app = {
...window.app,
renderActivity: () => this.renderActivity(),
recordActivity: (type, desc, extra) => this.recordActivity(type, desc, extra),
filterActivities: () => this.filterActivities(),
deleteSingleActivity: (id) => this.deleteSingleActivity(id),
clearAllActivities: () => this.clearAllActivities(),
exportActivities: () => this.exportActivities()
};
🔌 OpenHarmony 原生代码
// ActivityPlugin.ets - 活动记录插件(扩展版)
import { webview } from '@kit.ArkWeb';
import { common } from '@kit.AbilityKit';
import { fileIo } from '@kit.CoreFileKit';
import { deviceInfo } from '@kit.DeviceInfoKit';
@NativeComponent
export class ActivityPlugin {
private context: common.UIAbilityContext;
private maxRecordCount = 1000; // 最大记录数限制
constructor(context: common.UIAbilityContext) {
this.context = context;
}
// 初始化插件(扩展方法注册)
public init(webviewController: webview.WebviewController): void {
webviewController.registerJavaScriptProxy(
new ActivityJSProxy(this),
'activityPlugin',
['recordActivity', 'getActivities', 'deleteActivity', 'clearAllActivities', 'getDeviceInfo']
);
}
// 记录活动(扩展版)
public recordActivity(type: string, description: string, extra?: any): Promise<boolean> {
return new Promise((resolve) => {
try {
const activitiesPath = this.context.cacheDir + '/activities.json';
let activities: Array<any> = [];
// 读取现有记录
try {
if (fileIo.accessSync(activitiesPath)) {
const content = fileIo.readTextSync(activitiesPath);
activities = JSON.parse(content);
}
} catch {
activities = [];
}
// 新增:获取设备信息
const deviceId = deviceInfo.getDeviceId() || 'unknown';
const deviceModel = deviceInfo.getModel() || 'unknown device';
// 构建完整活动记录
const activity = {
id: Date.now(),
type: type,
description: description,
createdAt: new Date().toISOString(),
device: `${deviceModel} (${deviceId.substring(0, 8)})`, // 设备信息脱敏
...(extra || {})
};
activities.push(activity);
// 新增:限制记录数量,自动清理旧记录
if (activities.length > this.maxRecordCount) {
// 按时间降序排序,保留最新的maxRecordCount条
activities.sort((a: any, b: any) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
);
activities = activities.slice(0, this.maxRecordCount);
}
// 写入文件
fileIo.writeTextSync(activitiesPath, JSON.stringify(activities, null, 2));
resolve(true);
} catch (error) {
console.error('Failed to record activity:', error);
resolve(false);
}
});
}
// 获取活动列表(扩展版:支持筛选)
public getActivities(filter?: {type?: string, keyword?: string}): Promise<Array<any>> {
return new Promise((resolve) => {
try {
const activitiesPath = this.context.cacheDir + '/activities.json';
if (!fileIo.accessSync(activitiesPath)) {
resolve([]);
return;
}
const content = fileIo.readTextSync(activitiesPath);
let activities = JSON.parse(content);
// 新增:筛选逻辑
if (filter) {
// 按类型筛选
if (filter.type && filter.type !== 'all') {
activities = activities.filter((act: any) => act.type === filter.type);
}
// 按关键词筛选
if (filter.keyword) {
const keyword = filter.keyword.toLowerCase();
activities = activities.filter((act: any) =>
act.description.toLowerCase().includes(keyword) ||
act.type.toLowerCase().includes(keyword)
);
}
}
// 按时间降序排序
activities.sort((a: any, b: any) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
);
resolve(activities);
} catch (error) {
console.error('Failed to get activities:', error);
resolve([]);
}
});
}
// 新增:删除单条记录
public deleteActivity(activityId: number): Promise<boolean> {
return new Promise((resolve) => {
try {
const activitiesPath = this.context.cacheDir + '/activities.json';
if (!fileIo.accessSync(activitiesPath)) {
resolve(false);
return;
}
const content = fileIo.readTextSync(activitiesPath);
let activities = JSON.parse(content);
// 过滤掉要删除的记录
activities = activities.filter((act: any) => act.id !== activityId);
fileIo.writeTextSync(activitiesPath, JSON.stringify(activities, null, 2));
resolve(true);
} catch (error) {
console.error('Failed to delete activity:', error);
resolve(false);
}
});
}
// 新增:清空所有记录
public clearAllActivities(): Promise<boolean> {
return new Promise((resolve) => {
try {
const activitiesPath = this.context.cacheDir + '/activities.json';
// 写入空数组
fileIo.writeTextSync(activitiesPath, '[]');
resolve(true);
} catch (error) {
console.error('Failed to clear activities:', error);
resolve(false);
}
});
}
// 新增:获取设备信息
public getDeviceInfo(): Promise<string> {
return new Promise((resolve) => {
try {
const model = deviceInfo.getModel() || '未知设备';
const brand = deviceInfo.getBrand() || '未知品牌';
resolve(`${brand} ${model}`);
} catch (error) {
console.error('Failed to get device info:', error);
resolve('未知设备');
}
});
}
}
// ActivityJSProxy.ets - JavaScript代理类(扩展版)
class ActivityJSProxy {
private plugin: ActivityPlugin;
constructor(plugin: ActivityPlugin) {
this.plugin = plugin;
}
// 异步包装:记录活动
async recordActivity(type: string, description: string, extra?: any): Promise<boolean> {
return await this.plugin.recordActivity(type, description, extra);
}
// 异步包装:获取活动列表
async getActivities(filter?: {type?: string, keyword?: string}): Promise<Array<any>> {
return await this.plugin.getActivities(filter);
}
// 异步包装:删除单条记录
async deleteActivity(activityId: number): Promise<boolean> {
return await this.plugin.deleteActivity(activityId);
}
// 异步包装:清空所有记录
async clearAllActivities(): Promise<boolean> {
return await this.plugin.clearAllActivities();
}
// 异步包装:获取设备信息
async getDeviceInfo(): Promise<string> {
return await this.plugin.getDeviceInfo();
}
}
📝 总结
活动记录功能实现了核心能力增强,主要优化点包括:
记录维度扩展:新增设备信息、操作溯源等字段,完善操作记录的完整性;
交互体验优化:支持按类型筛选、关键词搜索,不同操作类型区分样式展示;
记录管理能力:新增单条删除、批量清空、导出记录等功能,支持记录数量限制;
数据安全保障:自动清理旧记录防止数据膨胀,敏感操作(删除)添加二次确认;
原生能力扩展:OpenHarmony 端新增设备信息获取、记录删除 / 清空等方法,与 Web 端逻辑对齐。
扩展后的代码保持原有简洁架构,同时增强了功能实用性和用户体验,符合 Cordova+OpenHarmony 混合开发的设计规范,满足用户对操作日志的精细化管理需求。
活动记录功能的筛选和搜索功能如何实现?
如何确保活动记录的准确性和完整性?
除了 JavaScript,还可以用哪些语言实现活动记录功能?
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)