作者:L、218
发布平台:CSDN
发布时间:2025年12月4日


🎯 导读:在前两篇文章中,我们用 Electron 模拟了鸿蒙的“服务卡片”和“超级终端”。本文将更进一步——构建一个真正意义上的“跨平台应用框架原型”,实现:

✅ 一套代码
✅ 同时输出:
  🔹 鸿蒙移动端应用(ArkTS)
  🔹 Electron 桌面端应用(TypeScript + React)

📌 关键词:统一业务逻辑 | 共享组件库 | 状态同步 | Monorepo 架构 | 鸿蒙 ArkUI vs Electron 渲染层


一、为什么需要“一次开发,多端运行”?

随着智能设备种类激增,开发者面临巨大挑战:

  • 手机、平板、车机、智慧屏 → 需要维护多个版本
  • Web、桌面、原生 → 技术栈割裂
  • 功能同步难、Bug 修复慢、体验不一致

而华为鸿蒙提出的 “一次开发,多端部署” 正是为了解决这一痛点。

但问题是:非鸿蒙生态的应用如何融入这个体系?

答案是:借助现代前端工程化能力,构建“类鸿蒙”开发范式。


二、目标架构图

              +---------------------+
              |   shared-core/      |
              |  - 业务逻辑         |
              |  - 数据模型         |
              |  - API 封装         |
              +----------+----------+
                         |
        +---------------v------------------+
        |           monorepo-root          |
        +----------------------------------+

     +------------+                    +------------------+
     |  harmony/  |                    |   electron-app/  |
     | - ArkTS    |<-- 共享逻辑 -->    | - React + TS     |
     | - ArkUI    |   npm link       | - Electron 主进程 |
     +------------+                    +------------------+
         ↓                                ↓
   鸿蒙手机/智慧屏                     Windows/macOS/Linux

📌 我们使用 Monorepo + TypeScript + 统一组件抽象层 实现跨端复用。


三、项目初始化:搭建 Monorepo 结构

使用 npm 原生支持 Workspaces

mkdir harmony-electron-monorepo
cd harmony-electron-monorepo

# 初始化根目录
npm init -y
npm pkg set workspaces='["shared-core", "harmony-adapter", "electron-app"]'

最终结构如下:

/harmony-electron-monorepo
├── package.json             # 启用 workspaces
├── shared-core/             # 核心共享模块
│   ├── index.ts
│   ├── models/
│   │   └── CardItem.ts
│   ├── services/
│   │   └── api-client.ts
│   └── utils/
│       └── formatter.ts
├── harmony-adapter/         # 鸿蒙端适配层
│   ├── src/main/ets/
│   │   └── MainAbility.ets
│   └── build-profile.json5
└── electron-app/            # Electron 应用
    ├── main.js
    ├── preload.js
    └── renderer/
        ├── App.tsx
        └── components/CardView.tsx

四、第一步:定义共享核心模块 (shared-core)

shared-core/models/CardItem.ts

export interface CardItem {
  id: string
  title: string
  content: string
  type: 'news' | 'todo' | 'weather' | 'custom'
  createdAt: number
  priority?: 'low' | 'medium' | 'high'
}

// 业务逻辑:生成推荐标题
export function generateCardTitle(type: CardItem['type']): string {
  const titles: Record<CardItem['type'], string> = {
    news: '最新资讯速递',
    todo: '待办事项提醒',
    weather: '今日天气预报',
    custom: '自定义卡片'
  }
  return titles[type]
}

// 工具函数:格式化时间
export function formatTime(timestamp: number): string {
  return new Date(timestamp).toLocaleString('zh-CN')
}

shared-core/services/api-client.ts

export interface ApiResponse<T> {
  success: boolean
  data: T
  message?: string
}

export async function fetchCards(): Promise<ApiResponse<CardItem[]>> {
  try {
    // 这里可以对接真实后端或 mock 数据
    const mockData: CardItem[] = [
      {
        id: '1',
        title: '欢迎使用跨端系统',
        content: '本数据来自 shared-core',
        type: 'news',
        createdAt: Date.now()
      }
    ]
    return { success: true, data: mockData }
  } catch (err) {
    return { success: false, data: [], message: '加载失败' }
  }
}

发布为本地包

# 在 shared-core 目录下
npm init -y
npm pkg set name="@my/shared-core" version="0.1.0" type="module"

然后在其他项目中通过 npm link 或直接引用。


五、第二步:鸿蒙端接入共享逻辑(harmony-adapter

虽然鸿蒙使用 ArkTS,但它兼容 TypeScript 语法子集。

创建 ArkTS 文件:MainAbility.ets

import router from '@ohos.router'
import { CardItem, fetchCards } from '@my/shared-core'

@Entry
@Component
struct CardListView {
  @State cardList: CardItem[] = []

  async aboutToAppear() {
    const result = await fetchCards()
    if (result.success) {
      this.cardList = result.data
    }
  }

  build() {
    Column({ space: 10 }) {
      Text('共享核心卡片列表').fontSize(20).fontWeight(FontWeight.Bold).margin(10)

      ForEach(this.cardList, (item: CardItem) => {
        this.CardItemComponent(item)
      }, (item: CardItem) => item.id)
    }.padding(10)
  }

  @Builder
  CardItemComponent(item: CardItem) {
    Column() {
      Text(item.title).fontSize(16).fontWeight(FontWeight.Medium)
      Text(item.content).fontSize(12).fontColor('#666').margin({ top: 4 })
      Text(`创建于:${formatTime(item.createdAt)}`).fontSize(10).fontColor('#999')
    }
    .padding(12)
    .width('100%')
    .backgroundColor('#f5f5f5')
    .borderRadius(8)
  }
}

// 注意:需将 shared-core 中的 formatTime 注入到全局或单独引入
function formatTime(timestamp: number): string {
  return new Intl.DateTimeFormat('zh-CN').format(new Date(timestamp))
}

⚠️ 实际打包时需通过构建工具将 @my/shared-core 编译进 ETS 工程(可用 tsc + copy 脚本实现)。


六、第三步:Electron 端使用同一套逻辑(electron-app

安装依赖

cd electron-app
npm install react react-dom typescript @types/react @types/node
npm link ../shared-core  # 或使用 npm link @my/shared-core

renderer/App.tsx

import React, { useEffect, useState } from 'react'
import { CardItem, fetchCards } from '@my/shared-core'
import CardView from './components/CardView'

const App: React.FC = () => {
  const [cards, setCards] = useState<CardItem[]>([])
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    const load = async () => {
      const result = await fetchCards()
      if (result.success) {
        setCards(result.data)
      }
      setLoading(false)
    }
    load()
  }, [])

  return (
    <div style={styles.container}>
      <h2>🎯 Electron 端 - 共享核心</h2>
      {loading ? (
        <p>加载中...</p>
      ) : (
        cards.map(card => <CardView key={card.id} card={card} />)
      )}
    </div>
  )
}

const styles = {
  container: {
    padding: '20px',
    fontFamily: 'Segoe UI, sans-serif',
    backgroundColor: '#fafafa'
  }
}

export default App

renderer/components/CardView.tsx

import React from 'react'
import { CardItem } from '@my/shared-core'

const CardView: React.FC<{ card: CardItem }> = ({ card }) => {
  return (
    <div style={styles.card}>
      <h3 style={styles.title}>{card.title}</h3>
      <p style={styles.content}>{card.content}</p>
      <small style={styles.time}>📅 {new Date(card.createdAt).toLocaleString()}</small>
    </div>
  )
}

const styles = {
  card: {
    padding: '16px',
    border: '1px solid #ddd',
    borderRadius: '12px',
    backgroundColor: 'white',
    boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
    marginBottom: '12px'
  },
  title: {
    margin: '0 0 8px 0',
    fontSize: '16px',
    color: '#333'
  },
  content: {
    margin: '0 0 8px 0',
    fontSize: '14px',
    color: '#555'
  },
  time: {
    color: '#999'
  }
}

export default CardView

七、启动脚本整合

根目录 package.json

💡 推荐使用 TurboRepo 或 Nx 替代原生 npm workspace 以获得更好的构建优化。


八、效果对比图

平台 截图 特点
鸿蒙模拟器 https://img-blog.csdnimg.cn/direct/7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d.png 使用 ArkUI 渲染,原生流畅
Electron 桌面 https://img-blog.csdnimg.cn/direct/5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t.png Web 技术栈,高度复用

📌 关键发现

两个平台展示的是完全相同的业务数据和逻辑处理结果,仅 UI 层根据平台特性做了适配。


九、优势与局限性分析

维度 优势 局限
✅ 代码复用率 高达 70%+ 业务逻辑可共享 UI 层仍需分别实现
✅ 维护成本 修改一处逻辑,两端同步生效 构建流程稍复杂
✅ 团队协作 前端 & 鸿蒙团队共用模型定义 初期学习曲线较陡
✅ 扩展性 可加入 Web、小程序等更多端 依赖良好抽象设计

十、未来演进方向

1. 引入 TSCONFIG PATH MAPPING 实现无缝导入

2. 使用 Capacitor 或 Tauri 替代 Electron 提升性能

Tauri 支持 Rust + Web,体积更小,安全性更高。

3. 接入鸿蒙 DevEco Studio 的 预览器插件机制

让设计师在一个环境中看到所有端的效果。

4. 构建“跨端组件库”

封装 ButtonCardList 等基础组件,自动适配平台风格。


结语

“不要等待完美的工具,去创造你想要的开发方式。”

鸿蒙的“一次开发,多端部署”是一个伟大的愿景。而我们作为开发者,完全可以用现有的技术栈——TypeScript、React、Electron、Monorepo——去逼近它、实践它、超越它。

真正的跨端,不是平台的统一,而是思维的打通。


开源地址

GitHub 仓库:https://github.com/L218/harmony-electron-monorepo
📦 包含完整可运行示例 + 构建脚本 + 多环境配置


📢 关注 @L、218,获取系列更新:

  • 下一篇预告:《用 WebSocket + MQTT 实现鸿蒙与 Electron 的实时双向通信》
  • 加入读者群:文末扫码进“鸿蒙×前端融合开发交流群”

💬 评论区互动:你希望“共享层”还包含哪些内容?状态管理(Redux/Zustand)?国际化?欢迎留言讨论!

本文原创,转载请注明出处。

 

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐