HarmonyOS应用开发实战:猫猫大作战-在前三篇中,我们用 `LazyForEach` + `@Reusable` + `cachedCount` 三
前言在前三篇中我们用LazyForEachReusablecachedCount三件套构建了高性能排行榜列表。所有这些能力都建立在IDataSource接口之上——它是LazyForEach的数据供给契约。但是多数教程只展示了最简单的数组包装一旦涉及分页加载、服务端同步、数据合并、增量更新等真实业务场景基础实现就不够用了。本文以「猫猫大作战」排行榜数据源为锚点从零实现一个企业级IDataSource——支持泛型、分页加载、服务端同步拉取、增量插入、搜索过滤、远程数据合并等高级能力。这也是阶段二的收官之作。提示本系列不讲 ArkTS 基础语法与环境搭建假设你已跟完第 1–69 篇。本篇是阶段二第 70 篇。一、IDataSource 基础回顾1.1 接口定义// kit.ArkUI 中的接口定义 interface IDataSource { totalCount(): number; getData(index: number): Object; registerDataChangeListener(listener: DataChangeListener): void; unregisterDataChangeListener(listener: DataChangeListener): void; } interface DataChangeListener { onDataReloaded(): void; onDataAdd(index: number): void; onDataMove(from: number, to: number): void; onDataDelete(index: number): void; onDataChange(index: number): void; }1.2 最简实现class SimpleDataSourceT implements IDataSource { private data: T[] []; private listeners: DataChangeListener[] []; constructor(data: T[]) { this.data data; } totalCount(): number { return this.data.length; } getData(index: number): T { return this.data[index]; } registerDataChangeListener(listener: DataChangeListener): void { if (!this.listeners.includes(listener)) this.listeners.push(listener); } unregisterDataChangeListener(listener: DataChangeListener): void { const idx this.listeners.indexOf(listener); if (idx 0) this.listeners.splice(idx, 1); } // 增量方法 push(item: T): void { this.data.push(item); this.listeners.forEach(l l.onDataAdd(this.data.length - 1)); } removeAt(index: number): void { this.data.splice(index, 1); this.listeners.forEach(l l.onDataDelete(index)); } updateAt(index: number, item: T): void { this.data[index] item; this.listeners.forEach(l l.onDataChange(index)); } reload(data: T[]): void { this.data data; this.listeners.forEach(l l.onDataReloaded()); } }但实际项目中这个简单实现远远不够。下面我们从真实需求出发逐步增强它。二、项目实战排行榜数据源增强2.1 基础数据模型// types/GameRecord.ets export class GameRecord { id: number; rank: number; playerName: string; score: number; avatar: string; mergeCount: number; maxCombo: number; gameDate: string; constructor(id: number, rank: number, name: string, score: number, avatar: string, merge: number, combo: number, date: string) { this.id id; this.rank rank; this.playerName name; this.score score; this.avatar avatar; this.mergeCount merge; this.maxCombo combo; this.gameDate date; } }2.2 阶段一泛型数据源 分页加载真实场景下排行榜数据往往来自服务端分页 API不能一次性加载所有数据// datasource/PagedDataSource.ets import { IDataSource, DataChangeListener } from kit.ArkUI; import { hilog } from kit.PerformanceAnalysisKit; const TAG PagedDataSource; const DOMAIN 0xFF00; export class PagedDataSourceT implements IDataSource { protected data: T[] []; protected listeners: DataChangeListener[] []; private currentPage: number 0; private pageSize: number 20; private hasMore: boolean true; private isLoading: boolean false; totalCount(): number { return this.data.length; } getData(index: number): T { return this.data[index]; } registerDataChangeListener(listener: DataChangeListener): void { if (!this.listeners.includes(listener)) this.listeners.push(listener); } unregisterDataChangeListener(listener: DataChangeListener): void { const idx this.listeners.indexOf(listener); if (idx 0) this.listeners.splice(idx, 1); } // 加载下一页由外部调用列表触底时触发 async loadNextPage(fetcher: (page: number, size: number) PromiseT[]): Promiseboolean { if (this.isLoading || !this.hasMore) return false; this.isLoading true; const nextPage this.currentPage 1; try { const newItems await fetcher(nextPage, this.pageSize); if (newItems.length 0) { this.hasMore false; return false; } const startIndex this.data.length; this.data.push(...newItems); this.currentPage nextPage; // 增量通知逐条通知 LazyForEach for (let i 0; i newItems.length; i) { this.listeners.forEach(l l.onDataAdd(startIndex i)); } hilog.info(DOMAIN, TAG, 加载第 ${nextPage} 页成功新增 ${newItems.length} 条); // 通知数据已变更适用于已经可见的项可能需要刷新 this.notifyDataChanged(); return true; } catch (err) { hilog.error(DOMAIN, TAG, 加载失败: ${JSON.stringify(err)}); return false; } finally { this.isLoading false; } } // 刷新下拉重新加载第一页 async refresh(fetcher: (page: number, size: number) PromiseT[]): Promisevoid { this.currentPage 0; this.hasMore true; const newItems await fetcher(1, this.pageSize); this.data newItems; this.currentPage 1; // 增量通知替代全量 reload避免组件重建闪烁 this.listeners.forEach(l l.onDataReloaded()); hilog.info(DOMAIN, TAG, 刷新完成共 ${this.data.length} 条); } // 是否还有更多数据 getHasMore(): boolean { return this.hasMore; } // 当前是否正在加载 getIsLoading(): boolean { return this.isLoading; } // 提供给子类覆盖的通知方法 protected notifyDataChanged(): void { /* 子类可按需实现 */ } }使用方式Entry Component struct LeaderboardPage { private dataSource new PagedDataSourceGameRecord(); private scroller: Scroller new Scroller(); aboutToAppear() { // 首次加载 this.dataSource.loadNextPage(this.fetchRankings.bind(this)); } // 服务端分页 API 封装 async fetchRankings(page: number, size: number): PromiseGameRecord[] { const resp await HttpRankingApi.getRankings(page, size); return resp.data.map((item, idx) new GameRecord( item.id, (page - 1) * size idx 1, item.playerName, item.score, item.avatar, item.mergeCount, item.maxCombo, item.gameDate )); } build() { Column() { List({ scroller: this.scroller }) { LazyForEach(this.dataSource, (item: GameRecord) { ListItem() { RecordCard({ record: item }) } }, (item: GameRecord) item.id.toString()) } .cachedCount(5) .width(100%) .layoutWeight(1) .onReachEnd(() { // 触底自动加载更多 this.dataSource.loadNextPage(this.fetchRankings.bind(this)); }) } .height(100%) } }2.3 阶段二搜索过滤数据源在排行榜基础上增加按玩家名搜索的能力// datasource/SearchableDataSource.ets export class SearchableDataSourceT extends PagedDataSourceT { private allData: T[] []; // 全量数据缓存 private keyword: string ; private filterFn: (item: T, keyword: string) boolean; constructor(filterFn: (item: T, keyword: string) boolean) { super(); this.filterFn filterFn; } // 注入全量数据实际项目中可能是从本地数据库加载 setAllData(data: T[]): void { this.allData data; this.applyFilter(); } // 设置搜索关键字 search(keyword: string): void { this.keyword keyword.trim().toLowerCase(); this.applyFilter(); } // 清空搜索 clearSearch(): void { this.keyword ; this.applyFilter(); } private applyFilter(): void { if (!this.keyword) { this.data [...this.allData]; } else { this.data this.allData.filter(item this.filterFn(item, this.keyword)); } this.listeners.forEach(l l.onDataReloaded()); } }使用方式Component struct SearchableRanking { private dataSource new SearchableDataSourceGameRecord( (item, keyword) item.playerName.toLowerCase().includes(keyword) ); build() { Column() { // 搜索框 TextInput({ placeholder: 搜索玩家名称... }) .onChange((value) { this.dataSource.search(value); }) .padding(8) .margin(8) List() { LazyForEach(this.dataSource, (item: GameRecord) { ListItem() { RecordCard({ record: item }) } }, (item: GameRecord) item.id.toString()) } .cachedCount(5) .width(100%) .layoutWeight(1) } .height(100%) } }三、通用泛型数据源基类综合前面的经验构建一个企业级生产可用的泛型数据源基类// datasource/BaseDataSource.ets import { IDataSource, DataChangeListener } from kit.ArkUI; import { hilog } from kit.PerformanceAnalysisKit; const TAG BaseDataSource; const DOMAIN 0xFF00; export class BaseDataSourceT implements IDataSource { protected data: T[] []; protected listeners: SetDataChangeListener new Set(); private lockReload: boolean false; // ---- IDataSource 接口实现 ---- totalCount(): number { return this.data.length; } getData(index: number): T { return this.data[index]; } registerDataChangeListener(listener: DataChangeListener): void { this.listeners.add(listener); } unregisterDataChangeListener(listener: DataChangeListener): void { this.listeners.delete(listener); } // ---- 批量变更方法 ---- // 全量替换数据 setData(newData: T[]): void { this.data newData; this.listeners.forEach(l l.onDataReloaded()); } // 追加多条 appendItems(items: T[]): void { const startIdx this.data.length; this.data.push(...items); for (let i 0; i items.length; i) { this.listeners.forEach(l l.onDataAdd(startIdx i)); } } // ---- 单条变更方法 ---- // 新增 add(item: T): number { const idx this.data.length; this.data.push(item); this.listeners.forEach(l l.onDataAdd(idx)); return idx; } // 插入到指定位置 insert(index: number, item: T): void { if (index 0 || index this.data.length) return; this.data.splice(index, 0, item); this.listeners.forEach(l l.onDataAdd(index)); } // 删除 remove(index: number): void { if (index 0 || index this.data.length) return; this.data.splice(index, 1); this.listeners.forEach(l l.onDataDelete(index)); } // 移除符合条件的第一个 removeFirst(predicate: (item: T) boolean): boolean { const idx this.data.findIndex(predicate); if (idx 0) { this.remove(idx); return true; } return false; } // 更新 update(index: number, item: T): void { if (index 0 || index this.data.length) return; this.data[index] item; this.listeners.forEach(l l.onDataChange(index)); } // 有条件地更新所有匹配项 updateAll(predicate: (item: T) boolean, updater: (item: T) T): void { for (let i 0; i this.data.length; i) { if (predicate(this.data[i])) { this.data[i] updater(this.data[i]); this.listeners.forEach(l l.onDataChange(i)); } } } // 移动 move(from: number, to: number): void { if (from to) return; const [item] this.data.splice(from, 1); this.data.splice(to, 0, item); this.listeners.forEach(l l.onDataMove(from, to)); } // 清空 clear(): void { this.data []; this.listeners.forEach(l l.onDataReloaded()); } // ---- 查询方法 ---- find(predicate: (item: T) boolean): T | undefined { return this.data.find(predicate); } findIndex(predicate: (item: T) boolean): number { return this.data.findIndex(predicate); } getAll(): readonly T[] { return this.data; } toArray(): T[] { return [...this.data]; } }变更方法对照表方法操作通知类型性能setData()全量替换onDataReloaded()⚠️ 重建所有可见组件appendItems()追加多条onDataAdd()× N✅ 仅新建组件add()单条追加onDataAdd()✅ 仅增一个insert()指定位置插入onDataAdd()✅ 仅增一个remove()删除onDataDelete()✅ 仅删一个update()修改onDataChange()✅ 仅刷新一个move()移动位置onDataMove()✅ 仅调整两个clear()清空onDataReloaded()✅ 移除所有子组件四、远程数据同步场景4.1 WebSocket 实时更新当排行榜需要在游戏进行中实时广播排名变化时// datasource/RealtimeDataSource.ets export class RealtimeDataSource extends BaseDataSourceGameRecord { private ws: WebSocket | null null; private reconnectTimer: number -1; // 连接 WebSocket 服务 connect(url: string): void { this.ws new WebSocket(url); this.ws.onopen () { hilog.info(DOMAIN, TAG, WebSocket 已连接); this.reconnectTimer -1; }; this.ws.onmessage (event: WebSocketMessageEvent) { const message JSON.parse(event.data as string); this.handleServerMessage(message); }; this.ws.onclose () { hilog.warn(DOMAIN, TAG, WebSocket 已断开5s 后重连); this.reconnectTimer setTimeout(() { this.connect(url); }, 5000); }; } private handleServerMessage(msg: { type: string; payload: any }): void { switch (msg.type) { case ranking_update: // 单条得分更新 const idx this.findIndex(r r.id msg.payload.id); if (idx 0) { const oldRank this.data[idx].rank; this.data[idx].score msg.payload.newScore; // 重新排序 this.data.sort((a, b) b.score - a.score); this.data.forEach((r, i) r.rank i 1); // 通知全量刷新因为排名顺序变化了 this.listeners.forEach(l l.onDataReloaded()); } break; case new_record: // 新纪录插入 const record new GameRecord( msg.payload.id, 0, msg.payload.playerName, msg.payload.score, msg.payload.avatar, msg.payload.mergeCount, msg.payload.maxCombo, msg.payload.gameDate ); this.data.push(record); this.data.sort((a, b) b.score - a.score); this.data.forEach((r, i) r.rank i 1); this.listeners.forEach(l l.onDataReloaded()); break; case ranking_full: // 全量排行榜数据 const records msg.payload.map((item: any) new GameRecord( item.id, item.rank, item.playerName, item.score, item.avatar, item.mergeCount, item.maxCombo, item.gameDate )); this.setData(records); break; } } disconnect(): void { this.ws?.close(); if (this.reconnectTimer 0) { clearTimeout(this.reconnectTimer); } } }4.2 数据合并策略当本地数据与服务端数据冲突时需要合并策略// 乐观更新 服务端确认 Entry Component struct LeaderboardPage { private dataSource new BaseDataSourceGameRecord(); // 玩家提交新得分 async submitScore(newScore: number): Promisevoid { // 1. 乐观更新立即在 UI 中更新 const playerId getPlayerId(); const idx this.dataSource.findIndex(r r.id playerId); if (idx 0) { const oldRecord this.dataSource.getData(idx); oldRecord.score newScore; this.dataSource.update(idx, oldRecord); } // 2. 发起服务端请求 try { const serverResult await RankingApi.submitScore(playerId, newScore); // 3. 服务端确认后用真实数据替换 const serverIdx this.dataSource.findIndex(r r.id serverResult.id); if (serverIdx 0) { this.dataSource.update(serverIdx, serverResult); } } catch (err) { // 4. 服务端失败 → 回滚 const rollbackIdx this.dataSource.findIndex(r r.id playerId); if (rollbackIdx 0) { const rollback await RankingApi.getPlayerRecord(playerId); this.dataSource.update(rollbackIdx, rollback); } } } }五、在 V2 状态管理中使用V2 模式下IDataSource同样适用ObservedV2 class GameRecordV2 { Trace id: number 0; Trace rank: number 0; Trace playerName: string ; Trace score: number 0; Trace avatar: string ; Trace mergeCount: number 0; Trace maxCombo: number 0; Trace gameDate: string ; } ComponentV2 struct LeaderboardPageV2 { private dataSource new PagedDataSourceGameRecordV2(); build() { Column() { List({ space: 8 }) { LazyForEach(this.dataSource, (item: GameRecordV2) { ListItem() { RecordCardV2({ record: item }) } }, (item: GameRecordV2) item.id.toString()) } .cachedCount(5) .width(100%) .layoutWeight(1) .onReachEnd(() { this.dataSource.loadNextPage(this.fetchPage.bind(this)); }) } .height(100%) } }V2 中关键区别数据源对象本身不需要装饰器但数据类需用ObservedV2Trace标记可观察属性这样更新后 UI 会自动刷新。六、IDataSource 设计模式总结6.1 架构图┌─────────── UI 层 ──────────┐ │ List / Grid / WaterFlow │ │ └── LazyForEach ──┘ │ └──────────┬─────────────────┘ │ IDataSource 契约 ┌──────────▼─────────────────┐ │ BaseDataSourceT │ ← 泛型基类提供完整 CRUD │ ├─ PagedDataSourceT │ ← 分页加载服务端分页 │ ├─ SearchableDataSource │ ← 搜索过滤 │ ├─ RealtimeDataSource │ ← WebSocket 实时更新 │ └─ CachedDataSource │ ← 磁盘缓存 服务端同步 └────────────────────────────┘6.2 选型指南场景推荐数据源说明本地数组 100 条BaseDataSource最简 CRUD服务端分页列表PagedDataSource触底自动加载需要搜索/过滤SearchableDataSource客户端过滤实时排名/消息RealtimeDataSourceWebSocket 推送离线优先服务端同步CachedDataSource本地存储 后台同步七、常见踩坑7.1 坑一通知错位// 错误先删除索引 2再删除索引 5 this.dataSource.remove(2); this.dataSource.remove(5); // ❌ 此时索引 5 已经变成原来的索引 6 // ✅ 正确从后往前删 this.dataSource.remove(5); this.dataSource.remove(2); // ✅ 从后往前索引不会偏移7.2 坑二register 和 unregister 不平衡每个LazyForEach使用数据源时都会register组件销毁时unregister。如果在页面退出后仍然调用数据源变更方法aboutToDisappear() { // 如果不清除引用后台 WebSocket 回调可能会操作已无监听器的数据源 this.dataSource.disconnect?.(); // ✅ 断开 WebSocket // 监听器由框架在 LazyForEach 销毁时自动 unregister }7.3 坑三频繁 onDataReloaded 导致闪烁// 错误每次小更新都调用 onDataReloaded this.data.sort((a, b) b.score - a.score); this.dataSource.setData(this.data); // ❌ onDataReloaded 重建所有可见组件闪烁 // ✅ 正确用 onDataMove onDataChange 精细更新 const fromIdx this.dataSource.findIndex(r r.id movedId); // ... 计算新位置 ... this.dataSource.move(fromIdx, toIdx); // ✅ 仅调整两个组件位置八、性能建议操作通知方式推荐场景增加 1 项onDataAdd新玩家加入批量增加循环onDataAdd分页加载下一页删除 1 项onDataDelete删除误录修改 1 项onDataChange更新得分移动 1 项onDataMove排名变化未跨页全量变更onDataReloaded下拉刷新 / 搜索不得已时优先使用增量通知onDataReloaded应尽量少用。九、阶段二总结#31–#70 回顾40 篇文章覆盖了状态管理、交互事件、动画体系、页面生命周期、列表性能优化五大子领域子专题文章范围核心技能状态管理 V1#31–#46State/Prop/Link/Provide/Observed/Watch状态管理 V2#47–#54Local/Param/Event/ObservedV2/Monitor/AppStorageV2动画体系#55–#60animateTo/属性动画/弹簧曲线/关键帧/共享元素网络与数据#61–#64HTTP/REST/JSON/SQLite页面生命周期#65–#66aboutToDisappear / onVisibleAreaChange列表性能优化#67–#70Reusable / LazyForEach / cachedCount / IDataSource下一篇预告第 71 篇开启阶段三页面架构 导航 弹窗从 EntryAbility 入口生命周期开始深入 Navigation 路由框架。如果这篇文章对你有帮助欢迎点赞、收藏⭐、关注你的支持是我持续创作的动力相关资源HarmonyOS LazyForEach 官方文档IDataSource API 参考懒加载优化最佳实践组件复用最佳实践开源鸿蒙跨平台社区第 67 篇Reusable 组件复用第 68 篇LazyForEach 按需渲染第 69 篇cachedCount 预加载

相关新闻

HarmonyOS应用开发实战:猫猫大作战-ArkUI 的条件渲染机制、`@State` 触发重渲、状态机思维

HarmonyOS应用开发实战:猫猫大作战-ArkUI 的条件渲染机制、`@State` 触发重渲、状态机思维

前言 前 9 篇我们拆完了主菜单的静态元素——标题、按钮、规则卡片、徽章、布局容器。但一个完整游戏不是「一页到底」,它要在主菜单 → 游戏页 → 暂停遮罩 → 结束页四种状态间切换。在 React/Vue 里你习惯 useState 条件渲染,在 HarmonyOS ArkUI 里…

2026/7/28 1:08:56阅读更多 →
云客服系统“消息太多管不过来”?智能分配与自动化工作流方案

云客服系统“消息太多管不过来”?智能分配与自动化工作流方案

文章摘要在线客服团队面临的核心矛盾不是人手不够,而是消息分配方式停留在“平均主义”阶段——高价值客户和普通咨询混在同一队列,资深客服和新手处理同等难度的会话。本文从技术实现视角,拆解云客服系统从“手动分配”到“智能调度”的升级…

2026/7/28 1:08:56阅读更多 →
2026大模型语音机器人选型指南:避开“伪智能”陷阱选对真AI

2026大模型语音机器人选型指南:避开“伪智能”陷阱选对真AI

文章摘要大模型语音机器人市场在2026年进入爆发期,但“伪智能”问题也随之凸显——不少产品仅在外层包装了大模型接口,核心对话引擎仍依赖传统关键词匹配。本文从技术架构视角拆解真伪AI语音机器人的四个关键区分维度:语音识别层、语义理解层…

2026/7/28 1:08:56阅读更多 →
ImageJ插件开发入门:用Java扩展开源图像处理软件的核心功能

ImageJ插件开发入门:用Java扩展开源图像处理软件的核心功能

ImageJ插件开发入门:用Java扩展开源图像处理软件的核心功能 【免费下载链接】ImageJ Public domain software for processing and analyzing scientific images 项目地址: https://gitcode.com/gh_mirrors/im/ImageJ ImageJ是一款功能强大的开源科学图像处理…

2026/7/28 2:19:05阅读更多 →
基于Eclipse Milo的轻量级OPC UA实施方案:创业第一天盈利

基于Eclipse Milo的轻量级OPC UA实施方案:创业第一天盈利

如果你正在工业自动化领域创业,或者负责工厂数字化转型项目,可能已经意识到:传统 OPC 系统虽然功能强大,但部署成本高、技术门槛陡峭,往往让中小型企业望而却步。更现实的是,很多创业团队在投入大量资源开发…

2026/7/28 2:19:05阅读更多 →
从源码到实践:objprint框架结构与核心组件详解

从源码到实践:objprint框架结构与核心组件详解

从源码到实践:objprint框架结构与核心组件详解 【免费下载链接】objprint A library that can print Python objects in human readable format 项目地址: https://gitcode.com/gh_mirrors/ob/objprint objprint是一个能够以人类可读格式打印Python对象的强大…

2026/7/28 2:19:05阅读更多 →
TI DRV8303EVM电机控制评估套件实战指南:从硬件解析到无传感器BLDC调试

TI DRV8303EVM电机控制评估套件实战指南:从硬件解析到无传感器BLDC调试

1. 项目概述与核心价值电机控制,尤其是针对永磁同步电机(PMSM)和无刷直流电机(BLDC)的高性能驱动,早已不是实验室里的前沿课题,而是渗透到我们日常工作和生活各个角落的成熟技术。从工厂里精准抓…

2026/7/28 2:19:05阅读更多 →
迷你版PySpectrometer制作指南:更小巧、更便携的光谱测量方案

迷你版PySpectrometer制作指南:更小巧、更便携的光谱测量方案

迷你版PySpectrometer制作指南:更小巧、更便携的光谱测量方案 【免费下载链接】PySpectrometer Raspberry Pi Spectrometer 项目地址: https://gitcode.com/gh_mirrors/py/PySpectrometer PySpectrometer是一款基于Raspberry Pi的开源光谱测量工具&#xff0…

2026/7/28 2:19:05阅读更多 →
TileLang与TVM:Python DSL实现GPU高性能计算与Tensor Core优化

TileLang与TVM:Python DSL实现GPU高性能计算与Tensor Core优化

在深度学习模型规模不断扩大的今天,如何高效利用GPU计算资源成为了开发者面临的核心挑战。传统CUDA编程门槛高、优化复杂,而现有高级框架往往难以充分发挥硬件潜力。TileLang作为一种创新的Python领域特定语言(DSL),通…

2026/7/28 2:17:04阅读更多 →
覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

🔹 工具基础介绍 OpenClaw 是开源生态中一款实用性较强的本地智能工具,凭借本地离线运行、可视化图形操作和任务自动化三大核心特性,赢得了众多用户的青睐。与普通在线对话AI工具不同,它属于能够直接操控本机软硬件的智能数字员工…

2026/7/27 1:14:34阅读更多 →
伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

所谓液压伺服阀体的精密激光焊接,是用激光束对阀座壳体(通常为不锈钢或铝合金)进行密封焊接,使阀体在21-35MPa的高压液压油或压缩气体中长期运行而不发生介质泄漏。液压伺服阀是高端液压系统的"大脑"。从航空航天飞行控…

2026/7/28 2:08:06阅读更多 →
D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 你是否还在…

2026/7/28 1:38:28阅读更多 →
告别臃肿!3步让你的暗影精灵笔记本重获新生

告别臃肿!3步让你的暗影精灵笔记本重获新生

告别臃肿!3步让你的暗影精灵笔记本重获新生 【免费下载链接】OmenSuperHub Control Omen laptop performance, fan speeds, and keyboard lighting, and unlock power limits. 项目地址: https://gitcode.com/gh_mirrors/om/OmenSuperHub 你是否也曾为官方Om…

2026/7/28 0:00:29阅读更多 →
RAG必踩坑!财报法规检索不准?这款开源工具让答案浮出水面,准确率飙升98.7%!

RAG必踩坑!财报法规检索不准?这款开源工具让答案浮出水面,准确率飙升98.7%!

做 RAG 的人应该都踩过这个致命的坑:把几百页的财报、法规、技术手册扔给向量库,问一个具体问题,搜出来的全是沾边但没用的内容 —— 关键信息要么被硬切块拆碎了,要么藏在几十条结果的最下面。语义相似≠真正相关,这个…

2026/7/28 0:00:29阅读更多 →
抖音视频文案提取工具全指南:免费2026版、手机App、在线工具一网打尽

抖音视频文案提取工具全指南:免费2026版、手机App、在线工具一网打尽

2026年做短视频运营,从抖音上扒文案早就不是偷偷抄笔记的事了。我刚开始做内容的时候,每天刷半小时抖音,手动把爆款视频的口播敲进备忘录,一条2分钟的视频得花十来分钟,碰到语速快的还要反复回听。后来试了一圈工具&am…

2026/7/28 0:00:29阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/27 16:57:54阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/26 19:05:21阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/26 19:05:21阅读更多 →