字数统计 Cordova 与 OpenHarmony 混合开发实战
本文介绍了字数统计功能的实现方案。该功能通过计算笔记字数并排序,展示字数排行榜,帮助用户了解写作量。文章提供了Web端的JavaScript实现代码和OpenHarmony原生代码两种方案,分别使用noteDB获取笔记数据并排序,以及通过文件IO读取笔记数据并排序。两种方案都实现了获取笔记列表、按字数排序和展示排行的核心流程,适用于混合开发场景。

📌 模块概述
字数统计功能为用户提供多维度的笔记字数分析能力,在基础字数排行的基础上,新增实时字数计算、分类统计、总字数汇总、筛选排序、可视化展示等实用功能,帮助用户全面了解笔记的写作量分布和创作趋势。
🔗 完整流程
第一步:数据计算
实时计算每条笔记的有效字数(过滤空格 / 标点,支持中 / 英文统计);
汇总所有笔记总字数、分类字数、平均字数等维度数据。
第二步:多维度排序
支持按字数(升序 / 降序)、创建时间、更新时间等维度排序;
可按分类筛选笔记,展示指定分类下的字数排行。
第三步:可视化展示
显示总字数、平均字数等核心统计指标;
展示带排名标识的字数排行榜,支持快速定位热门笔记;
提供分类字数占比的简易可视化展示。
🔧 Web代码实现
// 字数统计页面(扩展版)
async renderWordCount() {
const allNotes = await noteDB.getAllNotes();
// 新增:实时计算每条笔记的字数(兼容已存储/未存储wordCount的情况)
const notesWithCount = allNotes.map(note => ({
...note,
wordCount: note.wordCount || this.calculateWordCount(note.content || '')
}));
// 新增:多维度统计数据
const totalWords = notesWithCount.reduce((sum, note) => sum + note.wordCount, 0);
const avgWords = notesWithCount.length > 0 ? Math.round(totalWords / notesWithCount.length) : 0;
const maxWordNote = notesWithCount.length > 0
? notesWithCount.reduce((max, note) => note.wordCount > max.wordCount ? note : max)
: { title: '无', wordCount: 0 };
// 新增:按分类汇总字数
const categoryStats = {};
notesWithCount.forEach(note => {
const category = note.category || '未分类';
if (!categoryStats[category]) {
categoryStats[category] = { count: 0, notes: 0 };
}
categoryStats[category].count += note.wordCount;
categoryStats[category].notes += 1;
});
// 按字数降序排序
const sortedNotes = [...notesWithCount].sort((a, b) => b.wordCount - a.wordCount);
return `
<div class="page active">
<div class="page-header">
<h1 class="page-title">📊 字数统计</h1>
</div>
<!-- 新增:核心统计卡片 -->
<div class="word-count-stats">
<div class="stat-card">
<p class="stat-label">总字数</p>
<p class="stat-value">${totalWords.toLocaleString()}</p>
</div>
<div class="stat-card">
<p class="stat-label">平均每篇</p>
<p class="stat-value">${avgWords} 字</p>
</div>
<div class="stat-card">
<p class="stat-label">最长笔记</p>
<p class="stat-value">${maxWordNote.wordCount} 字</p>
</div>
<div class="stat-card">
<p class="stat-label">笔记总数</p>
<p class="stat-value">${notesWithCount.length}</p>
</div>
</div>
<!-- 新增:分类筛选 -->
<div class="word-count-filter" style="margin: 16px 0;">
<select id="category-filter" class="form-control" onchange="app.filterWordCount()">
<option value="all">全部分类</option>
${Object.keys(categoryStats).map(cat =>
`<option value="${cat}">${cat} (${categoryStats[cat].count}字)</option>`
).join('')}
</select>
<select id="sort-filter" class="form-control" style="margin-top: 8px;" onchange="app.filterWordCount()">
<option value="desc">字数从多到少</option>
<option value="asc">字数从少到多</option>
<option value="createTime">按创建时间</option>
</select>
</div>
<!-- 字数排行榜 -->
<div class="word-count-list" id="word-count-list">
${sortedNotes.map((note, index) => `
<div class="word-count-item ${index < 3 ? 'top-rank' : ''}">
<span class="rank">#${index + 1}</span>
<div class="word-count-content">
<span class="title">${Utils.escapeHtml(note.title || '无标题')}</span>
<span class="category">${note.category || '未分类'}</span>
</div>
<div class="word-count-footer">
<span class="count">${note.wordCount.toLocaleString()} 字</span>
<span class="date">${Utils.formatDate(note.createdAt)}</span>
</div>
</div>
`).join('') || '<div class="empty-state">暂无笔记数据</div>'}
</div>
</div>
`;
}
// 新增:实时计算字数(过滤空格/标点,支持中英文)
calculateWordCount(content) {
if (!content) return 0;
// 移除空格、换行、制表符
const cleanContent = content.replace(/\s+/g, '');
// 匹配中文字符 + 英文字母/数字
const chineseChars = cleanContent.match(/[\u4e00-\u9fa5]/g) || [];
const englishChars = cleanContent.match(/[a-zA-Z0-9]/g) || [];
// 中文按1字计算,英文/数字按1字符计算(可根据需求调整)
return chineseChars.length + englishChars.length;
}
// 新增:筛选和排序字数统计
async filterWordCount() {
try {
const category = document.getElementById('category-filter').value;
const sortType = document.getElementById('sort-filter').value;
const allNotes = await noteDB.getAllNotes();
// 计算字数 + 筛选分类
let filteredNotes = allNotes
.map(note => ({
...note,
wordCount: note.wordCount || this.calculateWordCount(note.content || '')
}))
.filter(note => category === 'all' || note.category === category);
// 按选择的维度排序
if (sortType === 'desc') {
filteredNotes.sort((a, b) => b.wordCount - a.wordCount);
} else if (sortType === 'asc') {
filteredNotes.sort((a, b) => a.wordCount - b.wordCount);
} else if (sortType === 'createTime') {
filteredNotes.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
}
// 更新列表DOM
const listContainer = document.getElementById('word-count-list');
listContainer.innerHTML = filteredNotes.map((note, index) => `
<div class="word-count-item ${index < 3 ? 'top-rank' : ''}">
<span class="rank">#${index + 1}</span>
<div class="word-count-content">
<span class="title">${Utils.escapeHtml(note.title || '无标题')}</span>
<span class="category">${note.category || '未分类'}</span>
</div>
<div class="word-count-footer">
<span class="count">${note.wordCount.toLocaleString()} 字</span>
<span class="date">${Utils.formatDate(note.createdAt)}</span>
</div>
</div>
`).join('') || '<div class="empty-state">暂无符合条件的笔记</div>';
} catch (error) {
console.error('筛选字数统计失败:', error);
Utils.showToast('筛选失败,请重试', 'error');
}
}
// 挂载全局方法
window.app = {
...window.app,
renderWordCount: () => this.renderWordCount(),
filterWordCount: () => this.filterWordCount()
};
🔌 OpenHarmony 原生代码
// WordCountPlugin.ets - 字数统计插件(扩展版)
import { webview } from '@kit.ArkWeb';
import { common } from '@kit.AbilityKit';
import { fileIo } from '@kit.CoreFileKit';
@NativeComponent
export class WordCountPlugin {
private context: common.UIAbilityContext;
constructor(context: common.UIAbilityContext) {
this.context = context;
}
// 初始化插件(扩展方法注册)
public init(webviewController: webview.WebviewController): void {
webviewController.registerJavaScriptProxy(
new WordCountJSProxy(this),
'wordCountPlugin',
['getWordCountRanking', 'calculateWordCount', 'getWordCountStats']
);
}
// 获取字数排行(扩展版:支持筛选和排序)
public getWordCountRanking(filter?: {category?: string, sort?: string}): Promise<Array<any>> {
return new Promise((resolve) => {
try {
const notesPath = this.context.cacheDir + '/notes.json';
if (!fileIo.accessSync(notesPath)) {
resolve([]);
return;
}
const content = fileIo.readTextSync(notesPath);
const notes = JSON.parse(content);
// 新增:实时计算字数
const notesWithCount = notes.map(note => ({
...note,
wordCount: note.wordCount || this.calculateWordCount(note.content || '')
}));
// 新增:分类筛选
let filteredNotes = notesWithCount;
if (filter?.category && filter.category !== 'all') {
filteredNotes = filteredNotes.filter(note => note.category === filter.category);
}
// 新增:多维度排序
if (filter?.sort === 'asc') {
filteredNotes.sort((a: any, b: any) => a.wordCount - b.wordCount);
} else if (filter?.sort === 'createTime') {
filteredNotes.sort((a: any, b: any) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
);
} else {
// 默认按字数降序
filteredNotes.sort((a: any, b: any) => b.wordCount - a.wordCount);
}
resolve(filteredNotes);
} catch (error) {
console.error('Failed to get word count ranking:', error);
resolve([]);
}
});
}
// 新增:原生端计算字数(兼容Web端逻辑)
public calculateWordCount(content: string): number {
if (!content) return 0;
// 移除空白字符
const cleanContent = content.replace(/\s+/g, '');
// 匹配中文字符
const chineseRegex = /[\u4e00-\u9fa5]/g;
const chineseChars = cleanContent.match(chineseRegex) || [];
// 匹配英文字母/数字
const englishRegex = /[a-zA-Z0-9]/g;
const englishChars = cleanContent.match(englishRegex) || [];
// 返回总字数
return chineseChars.length + englishChars.length;
}
// 新增:获取核心统计数据
public getWordCountStats(): Promise<{
totalWords: number,
avgWords: number,
maxWordNote: any,
categoryStats: Record<string, {count: number, notes: number}>
}> {
return new Promise((resolve) => {
try {
const notesPath = this.context.cacheDir + '/notes.json';
if (!fileIo.accessSync(notesPath)) {
resolve({
totalWords: 0,
avgWords: 0,
maxWordNote: { title: '无', wordCount: 0 },
categoryStats: {}
});
return;
}
const content = fileIo.readTextSync(notesPath);
const notes = JSON.parse(content);
const notesWithCount = notes.map(note => ({
...note,
wordCount: note.wordCount || this.calculateWordCount(note.content || '')
}));
// 计算总字数
const totalWords = notesWithCount.reduce((sum: number, note: any) => sum + note.wordCount, 0);
// 计算平均字数
const avgWords = notesWithCount.length > 0
? Math.round(totalWords / notesWithCount.length)
: 0;
// 找到最长笔记
let maxWordNote = { title: '无', wordCount: 0 };
if (notesWithCount.length > 0) {
maxWordNote = notesWithCount.reduce((max: any, note: any) =>
note.wordCount > max.wordCount ? note : max
);
}
// 分类统计
const categoryStats: Record<string, {count: number, notes: number}> = {};
notesWithCount.forEach((note: any) => {
const category = note.category || '未分类';
if (!categoryStats[category]) {
categoryStats[category] = { count: 0, notes: 0 };
}
categoryStats[category].count += note.wordCount;
categoryStats[category].notes += 1;
});
resolve({
totalWords,
avgWords,
maxWordNote,
categoryStats
});
} catch (error) {
console.error('Failed to get word count stats:', error);
resolve({
totalWords: 0,
avgWords: 0,
maxWordNote: { title: '无', wordCount: 0 },
categoryStats: {}
});
}
});
}
}
// WordCountJSProxy.ets - JavaScript代理类(扩展版)
class WordCountJSProxy {
private plugin: WordCountPlugin;
constructor(plugin: WordCountPlugin) {
this.plugin = plugin;
}
// 异步包装:获取字数排行
async getWordCountRanking(filter?: {category?: string, sort?: string}): Promise<Array<any>> {
return await this.plugin.getWordCountRanking(filter);
}
// 异步包装:计算字数
async calculateWordCount(content: string): Promise<number> {
return this.plugin.calculateWordCount(content);
}
// 异步包装:获取统计数据
async getWordCountStats(): Promise<any> {
return await this.plugin.getWordCountStats();
}
}
📝 总结
字数统计功能实现了核心能力增强,主要优化点包括:
数据计算升级:新增实时字数计算逻辑,过滤空格 / 标点,精准统计中 / 英文内容;
多维度统计:展示总字数、平均字数、最长笔记等核心指标,按分类汇总字数分布;
交互体验优化:支持按分类筛选、多维度排序,排行榜样式分层(前三名高亮);
原生能力扩展:OpenHarmony 端新增字数计算、统计汇总等方法,与 Web 端逻辑保持一致;
空状态处理:补充无数据时的友好提示,提升功能健壮性。
扩展后的代码保持原有架构简洁性,同时增强了功能实用性和用户体验,符合 Cordova+OpenHarmony 混合开发的设计规范。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)