鸿蒙ArkTS电子书阅读应用开发方案

一、应用概述

本方案基于鸿蒙ArkTS开发一款功能完备的电子书阅读应用,实现"一次开发,多端部署",支持手机、平板、PC等多设备无缝接续阅读。应用包含阅读、书架、书城、分类、我的五大核心模块,并创新性集成文字朗读、应用接续、网络加速等功能,满足现代用户多样化的阅读需求。

二、系统架构设计

2.1 整体架构

应用采用分层架构设计,确保功能解耦和多端适配能力:

  1. 表现层:基于ArkUI实现跨设备UI,根据屏幕尺寸自动适配
  2. 业务层:核心业务逻辑模块,包括五大核心功能模块
  3. 数据层:本地存储与分布式数据同步
  4. 基础服务层:提供朗读、网络加速等基础服务

2.2 技术选型

  • 开发语言:ArkTS
  • UI框架:ArkUI
  • 状态管理:@State、@Link、@Provide/@Consume
  • 分布式能力:DistributedDataManager、DeviceManager
  • 本地存储:Preferences、RelationalStore
  • 网络请求:@ohos.net.http
  • 多媒体:@ohos.multimedia.audio

三、核心功能实现

3.1 应用工程结构

复制代码
entry/src/main/ets/
├── common/                 # 公共资源和工具类
│   ├── constants/          # 常量定义
│   ├── utils/              # 工具函数
│   └── model/              # 数据模型
├── pages/                  # 页面组件
│   ├── reader/            # 阅读模块
│   ├── bookshelf/          # 书架模块
│   ├── bookstore/          # 书城模块
│   ├── category/           # 分类模块
│   └── profile/            # 我的模块
├── services/               # 服务类
│   ├── bookService.ets     # 书籍数据服务
│   ├── syncService.ets     # 分布式同步服务
│   ├── audioService.ets    # 朗读服务
│   └── networkService.ets  # 网络服务
└── entryability/           # 应用入口

3.2 数据模型设计

typescript 复制代码
// common/model/Book.ts
export interface Book {
  id: string;
  title: string;
  author: string;
  cover: string;
  progress: number; // 阅读进度(0-100)
  lastReadPosition: number; // 上次阅读位置
  lastReadTime: number; // 上次阅读时间戳
  chapters: Chapter[];
  category: string[];
  isLocal: boolean; // 是否本地书籍
  downloadStatus: 'none' | 'downloading' | 'complete' | 'failed';
}

export interface Chapter {
  id: string;
  bookId: string;
  title: string;
  content: string;
  pageCount: number;
}

export interface ReadingProgress {
  bookId: string;
  position: number;
  timestamp: number;
  deviceId: string;
}

3.3 分布式数据同步实现

实现多设备间阅读进度无缝接续:

typescript 复制代码
// services/syncService.ets
import distributedData from '@ohos.data.distributedData';
import deviceManager from '@ohos.distributedDeviceManager';
import { ReadingProgress } from '../common/model/Book';

export class SyncService {
  private kvManager: distributedData.KVManager | null = null;
  private kvStore: distributedData.KVStore | null = null;
  private deviceId: string = '';
  
  async init() {
    // 初始化分布式KV存储
    try {
      this.kvManager = await distributedData.createKVManager({
        context: getContext(),
        bundleName: 'com.example.ebookreader'
      });
      
      this.kvStore = await this.kvManager.getKVStore('reading_progress');
      
      // 获取当前设备ID
      const dm = await deviceManager.createDeviceManager('com.example.ebookreader');
      this.deviceId = dm.localDeviceInfo.deviceId;
      
      // 订阅数据变化
      this.subscribeProgressChanges();
    } catch (err) {
      console.error(`SyncService init failed: ${JSON.stringify(err)}`);
    }
  }
  
  // 保存阅读进度
  async saveReadingProgress(progress: ReadingProgress) {
    if (!this.kvStore) return;
    
    try {
      progress.deviceId = this.deviceId;
      await this.kvStore.put(`progress_${progress.bookId}`, JSON.stringify(progress));
    } catch (err) {
      console.error(`Save reading progress failed: ${JSON.stringify(err)}`);
    }
  }
  
  // 获取最新阅读进度
  async getLatestProgress(bookId: string): Promise<ReadingProgress | null> {
    if (!this.kvStore) return null;
    
    try {
      const value = await this.kvStore.get(`progress_${bookId}`);
      if (value) {
        return JSON.parse(value as string) as ReadingProgress;
      }
      return null;
    } catch (err) {
      console.error(`Get reading progress failed: ${JSON.stringify(err)}`);
      return null;
    }
  }
  
  // 订阅进度变化
  private subscribeProgressChanges() {
    if (!this.kvStore) return;
    
    this.kvStore.on('dataChange', (data) => {
      if (data.type === distributedData.DataChangeType.CHANGED) {
        // 通知阅读页面更新进度
        const progress = JSON.parse(data.value as string) as ReadingProgress;
        if (progress.deviceId !== this.deviceId) {
          // 发送事件通知
          eventHub.emit('progress_updated', progress);
        }
      }
    });
  }
}

// 全局实例
export const syncService = new SyncService();

3.4 阅读模块实现

typescript 复制代码
// pages/reader/ReaderPage.ets
import { Component, Prop, State, LazyForEach, ForEach, onPageShow } from '@ohos:arkui';
import { Book, Chapter } from '../../common/model/Book';
import { syncService } from '../../services/syncService';
import { audioService } from '../../services/audioService';
import router from '@ohos.router';

@Entry
@Component
struct ReaderPage {
  @Prop book: Book;
  @State currentChapter: Chapter = {} as Chapter;
  @State currentPage: number = 0;
  @State isReading: boolean = true;
  @State isPlaying: boolean = false;
  @State fontSize: number = 16;
  @State theme: 'light' | 'dark' | 'sepia' = 'light';
  
  private chapters: Chapter[] = [];
  private totalPages: number = 0;
  private pageSize: number = 0;
  private contentHeight: number = 0;
  
  async aboutToAppear() {
    // 初始化同步服务
    await syncService.init();
    
    // 获取最新阅读进度
    const progress = await syncService.getLatestProgress(this.book.id);
    if (progress) {
      this.currentPage = progress.position;
    } else {
      this.currentPage = this.book.lastReadPosition;
    }
    
    // 获取章节内容
    this.chapters = this.book.chapters;
    if (this.chapters.length > 0) {
      this.currentChapter = this.chapters[0];
      this.calculatePageCount();
    }
    
    // 订阅进度更新事件
    eventHub.on('progress_updated', (progress: ReadingProgress) => {
      if (progress.bookId === this.book.id) {
        this.currentPage = progress.position;
      }
    });
  }
  
  onPageShow() {
    // 页面显示时保存阅读进度
    this.saveProgress();
  }
  
  aboutToDisappear() {
    // 页面消失时保存阅读进度
    this.saveProgress();
    audioService.stop();
  }
  
  // 计算页数
  private calculatePageCount() {
    // 实际应用中应根据字体大小、屏幕尺寸计算页数
    this.pageSize = 500; // 假设每页500字
    this.totalPages = Math.ceil(this.currentChapter.content.length / this.pageSize);
  }
  
  // 获取当前页内容
  private getCurrentPageContent(): string {
    const start = this.currentPage * this.pageSize;
    const end = start + this.pageSize;
    return this.currentChapter.content.substring(start, end);
  }
  
  // 翻页
  private nextPage() {
    if (this.currentPage < this.totalPages - 1) {
      this.currentPage++;
      this.saveProgress();
    } else if (this.chapters.indexOf(this.currentChapter) < this.chapters.length - 1) {
      // 下一章
      const index = this.chapters.indexOf(this.currentChapter);
      this.currentChapter = this.chapters[index + 1];
      this.currentPage = 0;
      this.calculatePageCount();
      this.saveProgress();
    }
  }
  
  private prevPage() {
    if (this.currentPage > 0) {
      this.currentPage--;
      this.saveProgress();
    } else if (this.chapters.indexOf(this.currentChapter) > 0) {
      // 上一章
      const index = this.chapters.indexOf(this.currentChapter);
      this.currentChapter = this.chapters[index - 1];
      this.currentPage = Math.ceil(this.currentChapter.content.length / this.pageSize) - 1;
      this.calculatePageCount();
      this.saveProgress();
    }
  }
  
  // 保存阅读进度
  private saveProgress() {
    const progress: ReadingProgress = {
      bookId: this.book.id,
      position: this.currentPage,
      timestamp: new Date().getTime(),
      deviceId: ''
    };
    syncService.saveReadingProgress(progress);
    
    // 更新书籍对象
    this.book.lastReadPosition = this.currentPage;
    this.book.lastReadTime = new Date().getTime();
    this.book.progress = Math.floor((this.currentPage / this.totalPages) * 100);
  }
  
  // 切换朗读状态
  private toggleReadAloud() {
    if (this.isPlaying) {
      audioService.stop();
      this.isPlaying = false;
    } else {
      audioService.start(this.getCurrentPageContent());
      this.isPlaying = true;
    }
  }
  
  // 切换主题
  private changeTheme(theme: 'light' | 'dark' | 'sepia') {
    this.theme = theme;
  }
  
  build() {
    Column() {
      // 顶部导航栏
      Row() {
        Button('返回')
          .onClick(() => {
            router.back();
          })
        Text(this.currentChapter.title)
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .layoutWeight(1)
        Button(this.isPlaying ? '暂停朗读' : '开始朗读')
          .onClick(() => this.toggleReadAloud())
      }
      .padding(10)
      .width('100%')
      
      // 阅读区域
      Scroll() {
        Text(this.getCurrentPageContent())
          .fontSize(this.fontSize)
          .lineHeight(this.fontSize * 1.5)
          .padding(20)
          .textAlign(TextAlign.Start)
          .backgroundColor(this.theme === 'dark' ? '#222' : this.theme === 'sepia' ? '#f4ecd8' : '#fff')
          .fontColor(this.theme === 'dark' ? '#fff' : '#333')
          .width('100%')
          .onAreaChange((oldValue, newValue) => {
            this.contentHeight = newValue.height;
          })
      }
      .layoutWeight(1)
      .backgroundColor(this.theme === 'dark' ? '#222' : this.theme === 'sepia' ? '#f4ecd8' : '#fff')
      
      // 底部控制栏
      Row() {
        Button('上一页')
          .onClick(() => this.prevPage())
          .layoutWeight(1)
        
        Text(`${this.currentPage + 1}/${this.totalPages}`)
          .fontSize(14)
        
        Button('下一页')
          .onClick(() => this.nextPage())
          .layoutWeight(1)
      }
      .padding(10)
      .width('100%')
      
      // 底部工具栏
      Row() {
        Button('字体')
          .onClick(() => {
            this.fontSize = this.fontSize === 16 ? 18 : this.fontSize === 18 ? 20 : 16;
          })
        
        Button('白天')
          .onClick(() => this.changeTheme('light'))
          .backgroundColor(this.theme === 'light' ? '#007DFF' : '#eee')
          .fontColor(this.theme === 'light' ? '#fff' : '#333')
        
        Button('夜间')
          .onClick(() => this.changeTheme('dark'))
          .backgroundColor(this.theme === 'dark' ? '#007DFF' : '#eee')
          .fontColor(this.theme === 'dark' ? '#fff' : '#333')
        
        Button('护眼')
          .onClick(() => this.changeTheme('sepia'))
          .backgroundColor(this.theme === 'sepia' ? '#007DFF' : '#eee')
          .fontColor(this.theme === 'sepia' ? '#fff' : '#333')
      }
      .padding(10)
      .width('100%')
    }
    .height('100%')
    .backgroundColor(this.theme === 'dark' ? '#222' : this.theme === 'sepia' ? '#f4ecd8' : '#fff')
  }
}

3.5 书架模块实现

typescript 复制代码
// pages/bookshelf/BookshelfPage.ets
import { Component, State, LazyForEach, onPageShow } from '@ohos:arkui';
import { Book } from '../../common/model/Book';
import { bookService } from '../../services/bookService';
import router from '@ohos.router';
import { syncService } from '../../services/syncService';

@Entry
@Component
struct BookshelfPage {
  @State books: Book[] = [];
  @State isLoading: boolean = true;
  
  async onPageShow() {
    // 初始化同步服务
    await syncService.init();
    // 加载书架数据
    this.loadBooks();
  }
  
  async loadBooks() {
    this.isLoading = true;
    try {
      this.books = await bookService.getBookshelf();
      // 按最后阅读时间排序
      this.books.sort((a, b) => b.lastReadTime - a.lastReadTime);
    } catch (err) {
      console.error(`Failed to load books: ${JSON.stringify(err)}`);
    } finally {
      this.isLoading = false;
    }
  }
  
  navigateToReader(book: Book) {
    router.pushUrl({
      url: 'pages/reader/ReaderPage',
      params: { book: book }
    });
  }
  
  navigateToBookstore() {
    router.pushUrl({ url: 'pages/bookstore/BookstorePage' });
  }
  
  build() {
    Column() {
      Text('我的书架')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin(15)
        .alignSelf(ItemAlign.Start)
      
      if (this.isLoading) {
        Progress({ value: 50, type: ProgressType.Linear })
          .width('90%')
          .height(5)
      } else if (this.books.length === 0) {
        Column() {
          Image($r('app.media.empty_bookshelf'))
            .width(200)
            .height(200)
          Text('书架还是空的,去书城看看吧')
            .fontSize(16)
            .margin(10)
          Button('去书城')
            .onClick(() => this.navigateToBookstore())
        }
        .layoutWeight(1)
        .justifyContent(FlexAlign.Center)
      } else {
        Grid() {
          LazyForEach(this.books, (book: Book) => {
            GridItem() {
              Column() {
                Stack() {
                  Image(book.cover)
                    .width('100%')
                    .height(180)
                    .objectFit(ImageFit.Cover)
                  
                  if (book.progress > 0) {
                    Row() {
                      Progress({ value: book.progress, type: ProgressType.Linear })
                        .width('100%')
                        .height(3)
                    }
                    .alignSelf(ItemAlign.End)
                  }
                  
                  if (book.downloadStatus === 'downloading') {
                    Stack() {
                      Circle()
                        .width(40)
                        .height(40)
                        .fill(Color.Black)
                        .opacity(0.7)
                      Progress({ value: 30, type: ProgressType.Ring })
                        .width(30)
                        .height(30)
                        .color(Color.White)
                    }
                    .alignSelf(ItemAlign.Center)
                  }
                }
                .borderRadius(8)
                .overflow(Overflow.Hidden)
                
                Text(book.title)
                  .fontSize(14)
                  .margin(5)
                  .maxLines(1)
                  .textOverflow({ overflow: TextOverflow.Ellipsis })
                
                Text(book.author)
                  .fontSize(12)
                  .fontColor('#888')
                  .maxLines(1)
                  .textOverflow({ overflow: TextOverflow.Ellipsis })
              }
              .padding(10)
              .onClick(() => this.navigateToReader(book))
            }
          }, (item) => item.id)
        }
        .columnsTemplate('1fr 1fr 1fr')
        .columnsGap(10)
        .rowsGap(15)
        .padding(10)
        .layoutWeight(1)
      }
      
      BottomTabBar()
    }
    .height('100%')
    .backgroundColor('#f5f5f5')
  }
}

3.6 文字朗读功能实现

typescript 复制代码
// services/audioService.ets
import audio from '@ohos.multimedia.audio';
import tts from '@ohos.tts';

export class AudioService {
  private player: audio.Player | null = null;
  private ttsEngine: tts.TtsEngine | null = null;
  private isPlaying: boolean = false;
  private currentText: string = '';
  
  async init() {
    try {
      // 初始化TTS引擎
      this.ttsEngine = await tts.createTtsEngine();
      await this.ttsEngine.setParams({
        volume: 1.0,
        pitch: 1.0,
        speed: 1.0
      });
      
      // 初始化音频播放器
      this.player = await audio.createPlayer();
    } catch (err) {
      console.error(`AudioService init failed: ${JSON.stringify(err)}`);
    }
  }
  
  async start(text: string) {
    if (!this.ttsEngine) {
      await this.init();
    }
    
    if (!this.ttsEngine) return;
    
    try {
      this.currentText = text;
      this.isPlaying = true;
      
      // 注册TTS事件回调
      this.ttsEngine.on('mark', (mark) => {
        console.log(`TTS mark reached: ${mark}`);
      });
      
      this.ttsEngine.on('complete', () => {
        this.isPlaying = false;
      });
      
      this.ttsEngine.on('error', (err) => {
        console.error(`TTS error: ${JSON.stringify(err)}`);
        this.isPlaying = false;
      });
      
      // 开始朗读
      await this.ttsEngine.speak(text);
    } catch (err) {
      console.error(`Failed to start TTS: ${JSON.stringify(err)}`);
      this.isPlaying = false;
    }
  }
  
  async stop() {
    if (this.ttsEngine && this.isPlaying) {
      try {
        await this.ttsEngine.stop();
        this.isPlaying = false;
      } catch (err) {
        console.error(`Failed to stop TTS: ${JSON.stringify(err)}`);
      }
    }
  }
  
  async pause() {
    if (this.ttsEngine && this.isPlaying) {
      try {
        await this.ttsEngine.pause();
        this.isPlaying = false;
      } catch (err) {
        console.error(`Failed to pause TTS: ${JSON.stringify(err)}`);
      }
    }
  }
  
  async resume() {
    if (this.ttsEngine && !this.isPlaying && this.currentText) {
      try {
        await this.ttsEngine.resume();
        this.isPlaying = true;
      } catch (err) {
        console.error(`Failed to resume TTS: ${JSON.stringify(err)}`);
      }
    }
  }
  
  setSpeed(speed: number) {
    if (this.ttsEngine) {
      this.ttsEngine.setParams({ speed });
    }
  }
  
  setVolume(volume: number) {
    if (this.ttsEngine) {
      this.ttsEngine.setParams({ volume });
    }
  }
}

// 全局实例
export const audioService = new AudioService();

3.7 网络加速实现

typescript 复制代码
// services/networkService.ets
import http from '@ohos.net.http';
import cache from '@ohos.data.cache';

export class NetworkService {
  private httpClient: http.HttpClient | null = null;
  private cacheManager: cache.Cache | null = null;
  private cacheSize: number = 10 * 1024 * 1024; // 10MB缓存
  private cacheExpireTime: number = 24 * 60 * 60 * 1000; // 24小时过期
  
  async init() {
    // 创建HTTP客户端
    this.httpClient = http.createHttpClient();
    
    // 配置HTTP客户端
    this.httpClient.on('headersReceive', (header) => {
      console.log(`Response header: ${JSON.stringify(header)}`);
    });
    
    // 初始化缓存
    this.cacheManager = await cache.createCache('ebook_cache', this.cacheSize);
  }
  
  async request(url: string, options?: http.HttpRequestOptions): Promise<any> {
    if (!this.httpClient || !this.cacheManager) {
      await this.init();
    }
    
    // 尝试从缓存获取
    const cacheKey = this.generateCacheKey(url, options);
    const cachedData = await this.cacheManager?.get(cacheKey);
    
    if (cachedData) {
      const data = JSON.parse(cachedData as string);
      // 检查缓存是否过期
      if (Date.now() - data.timestamp < this.cacheExpireTime) {
        console.log(`Using cached data for ${url}`);
        return data.response;
      }
    }
    
    // 缓存未命中,发起网络请求
    try {
      const response = await this.httpClient?.request(url, options);
      
      if (response?.responseCode === 200) {
        // 缓存响应数据
        await this.cacheManager?.put(cacheKey, JSON.stringify({
          timestamp: Date.now(),
          response: response.result
        }));
      }
      
      return response?.result;
    } catch (err) {
      console.error(`Network request failed: ${JSON.stringify(err)}`);
      // 如果有缓存但已过期,仍然返回过期缓存
      if (cachedData) {
        console.log(`Using expired cache for ${url}`);
        return JSON.parse(cachedData as string).response;
      }
      throw err;
    }
  }
  
  private generateCacheKey(url: string, options?: http.HttpRequestOptions): string {
    // 根据URL和请求参数生成唯一缓存键
    let key = url;
    if (options?.method) key += `_${options.method}`;
    if (options?.extraData) key += `_${JSON.stringify(options.extraData)}`;
    return key;
  }
  
  // 清除缓存
  async clearCache() {
    if (this.cacheManager) {
      await this.cacheManager.clear();
    }
  }
  
  // 取消所有请求
  cancelAllRequests() {
    if (this.httpClient) {
      this.httpClient.cancelAllRequests();
    }
  }
}

// 全局实例
export const networkService = new NetworkService();

四、应用接续功能实现

应用接续功能允许用户在一个设备上暂停阅读,然后在另一个设备上继续阅读,实现无缝切换体验。

typescript 复制代码
// services/continuationService.ets
import abilityManager from '@ohos.app.ability.abilityManager';
import { Want } from '@ohos.app.ability.Want';
import { Book } from '../common/model/Book';
import { syncService } from './syncService';

export class ContinuationService {
  async continueOnOtherDevice(book: Book) {
    try {
      // 获取可用设备列表
      const deviceList = await abilityManager.getConnectedDevices('phone', 'tablet', 'pc');
      
      if (deviceList.length === 0) {
        console.log('No other devices available');
        return false;
      }
      
      // 选择第一个可用设备
      const targetDevice = deviceList[0];
      
      // 构建接续请求
      const want: Want = {
        bundleName: 'com.example.ebookreader',
        abilityName: 'ReaderAbility',
        parameters: {
          bookId: book.id,
          fromContinuation: true
        }
      };
      
      // 发起应用接续
      const result = await abilityManager.startAbilityOnDevice(targetDevice.deviceId, want);
      return result;
    } catch (err) {
      console.error(`Continuation failed: ${JSON.stringify(err)}`);
      return false;
    }
  }
  
  // 在应用启动时检查是否有接续请求
  async checkContinuationRequest(want: Want): Promise<string | null> {
    if (want.parameters?.fromContinuation && want.parameters?.bookId) {
      // 获取最新阅读进度
      const progress = await syncService.getLatestProgress(want.parameters.bookId as string);
      if (progress) {
        return want.parameters.bookId as string;
      }
    }
    return null;
  }
}

// 全局实例
export const continuationService = new ContinuationService();

五、多端适配实现

利用ArkTS的响应式布局和设备能力检测,实现应用在不同设备上的自适应显示:

typescript 复制代码
// common/utils/deviceUtil.ets
import device from '@ohos.device';
import display from '@ohos.display';

export class DeviceUtil {
  // 获取设备类型
  static getDeviceType(): 'phone' | 'tablet' | 'pc' | 'watch' | 'unknown' {
    const deviceType = device.deviceType;
    if (deviceType === 'phone') return 'phone';
    if (deviceType === 'tablet') return 'tablet';
    if (deviceType === 'pc') return 'pc';
    if (deviceType === 'watch') return 'watch';
    return 'unknown';
  }
  
  // 判断是否为大屏幕设备
  static isLargeScreen(): boolean {
    const defaultDisplay = display.getDefaultDisplaySync();
    const width = defaultDisplay.width;
    const height = defaultDisplay.height;
    const diagonal = Math.sqrt(width * width + height * height) / defaultDisplay.densityDpi * 25.4;
    return diagonal >= 10; // 10英寸以上视为大屏幕
  }
  
  // 获取合适的网格列数
  static getGridColumns(): number {
    if (this.getDeviceType() === 'phone') return 3;
    if (this.getDeviceType() === 'tablet') return 4;
    if (this.getDeviceType() === 'pc') return 6;
    return 2;
  }
  
  // 获取合适的字体大小
  static getFontSize(baseSize: number): number {
    if (this.isLargeScreen()) {
      return baseSize * 1.2;
    }
    return baseSize;
  }
}

六、总结

本方案基于鸿蒙ArkTS开发了一款功能完备的电子书阅读应用,实现了五大核心模块和三大创新功能:

  1. 核心功能:阅读、书架、书城、分类、我的
  2. 创新功能:多设备无缝接续阅读、文字朗读、网络加速
  3. 技术亮点:分布式数据同步、响应式布局、状态管理、缓存策略

应用充分利用鸿蒙系统的分布式能力,实现了跨设备数据同步和应用接续,为用户提供了无缝的阅读体验。同时,通过网络加速和缓存策略提升了内容加载速度,文字朗读功能满足了用户多样化的阅读需求。

相关推荐
小小ken3 小时前
鸿蒙模拟器提示:未开启hyper-v。运行模拟器需要开启hyper-v虚拟化支持。
华为·harmonyos·hyper-v·虚拟机
HwJack203 小时前
HarmonyOS开发中@AnimatableExtend装饰器:把动画做成“乐高”,告别复制粘贴的痛
华为·harmonyos
浮芷.3 小时前
Flutter 框架跨平台鸿蒙开发 - 急救指南应用
学习·flutter·华为·harmonyos·鸿蒙
提子拌饭1333 小时前
液相色谱质谱联用(LC-MS)数据可视化引擎:基于鸿蒙Flutter的高精度色谱卡与多维峰值拟合架构
flutter·华为·信息可视化·开源·harmonyos·鸿蒙
Utopia^3 小时前
Flutter 框架跨平台鸿蒙开发 - 社交星系
flutter·华为·harmonyos
亘元有量-流量变现3 小时前
深度技术对比:Android、iOS、鸿蒙(HarmonyOS)权限管理全解析
android·ios·harmonyos·方糖试玩
2301_822703203 小时前
生命科学大分子资产模拟交易系统:基于鸿蒙Flutter跨端架构的高频订单簿与K线图渲染引擎
flutter·华为·架构·开源·harmonyos·鸿蒙
前端不太难4 小时前
鸿蒙游戏开发的正确分层方式
华为·状态模式·harmonyos
以太浮标4 小时前
华为eNSP模拟器综合实验之- 华为USG6000V防火墙配置防御DoS攻击实战案例解析
运维·网络协议·网络安全·华为·信息与通信