Vue3 + Electron + OpenHarmony 跨平台实战:从架构设计到 Markdown 编辑器完整实现

Vue3 + Electron + OpenHarmony 跨平台实战:从架构设计到 Markdown 编辑器完整实现

欢迎加入开源鸿蒙PC社区:
https://harmonypc.csdn.net/

项目 Git 仓库:
https://atomgit.com/liboqian/Markdown

目录

  • 一、前言与技术背景
  • 二、技术栈选型与对比分析
    • [2.1 前端框架选型](#2.1 前端框架选型)
    • [2.2 桌面应用框架对比](#2.2 桌面应用框架对比)
    • [2.3 OpenHarmony 生态价值](#2.3 OpenHarmony 生态价值)
  • 三、项目架构设计
    • [3.1 三层架构模型](#3.1 三层架构模型)
    • [3.2 目录结构规范](#3.2 目录结构规范)
  • 四、核心功能实现详解
    • [4.1 Vue3 Composition API 实践](#4.1 Vue3 Composition API 实践)
    • [4.2 Electron IPC 通信机制](#4.2 Electron IPC 通信机制)
    • [4.3 OpenHarmony 原生能力封装](#4.3 OpenHarmony 原生能力封装)
  • [五、实战案例:Markdown 编辑器开发](#五、实战案例:Markdown 编辑器开发)
    • [5.1 Markdown 解析与实时预览](#5.1 Markdown 解析与实时预览)
    • [5.2 PDF 导出功能实现](#5.2 PDF 导出功能实现)
    • [5.3 HTML 导出功能实现](#5.3 HTML 导出功能实现)
  • 六、性能优化与最佳实践
    • [6.1 渲染性能优化](#6.1 渲染性能优化)
    • [6.2 内存管理与泄漏防护](#6.2 内存管理与泄漏防护)
    • [6.3 打包体积优化](#6.3 打包体积优化)
  • 七、常见问题排查指南
    • [7.1 开发环境配置](#7.1 开发环境配置)
    • [7.2 打包部署问题](#7.2 打包部署问题)
    • [7.3 跨平台兼容处理](#7.3 跨平台兼容处理)
  • 八、总结与未来展望

一、前言与技术背景





随着前端技术的飞速发展和跨平台需求的日益增长,现代桌面应用开发已经从传统的原生开发模式逐步转向基于 Web 技术的混合开发模式。这种转变不仅大幅降低了开发成本,还实现了代码的高复用性和快速迭代能力。

在当前的技术生态中,Electron 凭借其成熟的跨平台能力和丰富的 Node.js 生态,已经成为桌面应用开发的事实标准。从 Visual Studio Code 到 Slack,从 Discord 到 Figma,众多知名产品都基于 Electron 构建。与此同时,OpenHarmony 作为华为开源的分布式操作系统,正在快速构建自己的应用生态,而将 Web 技术栈引入 OpenHarmony 生态,成为了连接两大生态的重要桥梁。

本文将深入讲解如何使用 Vue3 + Electron + OpenHarmony 技术栈,从零开始构建一个功能完整的跨平台桌面应用。通过实战开发一个支持实时预览、PDF/HTML 导出的 Markdown 编辑器,你将掌握以下核心技能:

  • Vue3 Composition API 的现代开发范式
  • Electron 进程间通信与原生能力调用
  • OpenHarmony Web 组件的集成与适配
  • Markdown 解析、渲染与导出技术
  • 性能优化与跨平台兼容性处理

提示:本文适合具有一定 Vue.js 和 Web 开发基础的开发者。如果你是初学者,建议先完成 Vue3 官方教程的学习,这将帮助你更好地理解本文内容。

二、技术栈选型与对比分析

2.1 前端框架选型

在当前前端框架三巨头(Vue、React、Angular)中,选择 Vue3 作为本文的技术栈基于以下考量:

对比维度 Vue3 React 18 Angular 17
学习曲线 平缓 中等 陡峭
响应式原理 Proxy 手动状态管理 RxJS/Observable
模板语法 声明式模板 JSX 声明式模板
体积(gzip) 33KB 42KB 143KB
性能评分 95+ 93+ 85+
TypeScript 支持 优秀 优秀 原生支持
生态完善度 极高

Vue3 的核心优势

  1. 响应式系统重构 :从 Object.defineProperty 升级到 Proxy,性能提升 2 倍以上
  2. Composition API:提供更灵活的逻辑复用方式,解决了 Options API 的代码组织问题
  3. Tree-shaking 支持:按需引入,构建体积减少 40%
  4. 更好的类型推导:全面使用 TypeScript 重写,开发体验更佳

以下是 Composition API 的典型使用场景:

typescript 复制代码
import { ref, computed, watch, onMounted } from 'vue'

// 组合式函数:可复用的状态逻辑
export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  
  const doubleCount = computed(() => count.value * 2)
  
  function increment() {
    count.value++
  }
  
  function decrement() {
    count.value--
  }
  
  watch(count, (newVal, oldVal) => {
    console.log(`Count changed from ${oldVal} to ${newVal}`)
  })
  
  onMounted(() => {
    console.log('Counter initialized:', initialValue)
  })
  
  return { count, doubleCount, increment, decrement }
}

2.2 桌面应用框架对比

在桌面应用开发领域,目前主流的跨平台方案包括:

框架 内核 体积 性能 生态 代表产品
Electron Chromium + Node.js ~130MB 中等 极丰富 VS Code, Slack
Tauri 系统 WebView + Rust ~5MB 优秀 成长中 Prism, FileDrop
NW.js Chromium + Node.js ~110MB 中等 较丰富 Gitter, Wakatime
Flutter Desktop Skia + Dart ~30MB 优秀 快速发展 Canonical Ubuntu

选择 Electron 的原因:

  • 生态成熟:npm 生态完全可用,插件资源丰富
  • 开发体验:调试工具完善,Chrome DevTools 直接使用
  • 兼容性:Chromium 内核保证 Web 标准兼容性
  • 社区活跃:文档完善,问题解决方案丰富

2.3 OpenHarmony 生态价值

OpenHarmony 为 Web 应用提供了以下核心能力:

  • Web 组件:基于 Chromium 内核的 Web 渲染引擎
  • JS API 桥接:通过 JSBridge 调用设备原生能力
  • 分布式软总线:多设备间的无缝协同与数据流转
  • 原子化服务:轻量级服务卡片,支持免安装运行

随着 OpenHarmony 4.0+ 版本的发布,Web 组件的兼容性和性能得到了显著提升,为 Vue3 应用在鸿蒙生态的运行提供了坚实基础。

三、项目架构设计

3.1 三层架构模型

良好的架构设计是项目成功的关键。本项目采用经典的三层架构模型:

复制代码
┌───────────────────────────────────────────────────────┐
│                   应用层 (Vue3)                        │
│  ┌────────────┐  ┌────────────┐  ┌────────────┐      │
│  │  UI 组件   │  │  状态管理   │  │  路由管理   │      │
│  └────────────┘  └────────────┘  └────────────┘      │
├───────────────────────────────────────────────────────┤
│                 适配层 (IPC Bridge)                     │
│  ┌────────────┐  ┌────────────┐  ┌────────────┐      │
│  │  preload   │  │  context   │  │  event     │      │
│  │   脚本     │  │   bridge   │  │   bus      │      │
│  └────────────┘  └────────────┘  └────────────┘      │
├───────────────────────────────────────────────────────┤
│                 原生层 (Electron/Ohos)                  │
│  ┌────────────┐  ┌────────────┐  ┌────────────┐      │
│  │  主进程    │  │  文件系统   │  │  设备能力   │      │
│  │  (Main)    │  │  网络请求   │  │  系统通知   │      │
│  └────────────┘  └────────────┘  └────────────┘      │
└───────────────────────────────────────────────────────┘

各层职责划分

  • 应用层:负责 UI 渲染、用户交互、业务逻辑
  • 适配层:负责进程间通信、API 封装、平台适配
  • 原生层:负责系统调用、文件操作、设备能力

3.2 目录结构规范

遵循关注点分离原则,项目目录结构如下:

复制代码
vue-app/
├── public/                          # 静态资源(不参与构建)
│   ├── sample-document.md           # 示例 Markdown 文档
│   └── favicon.ico
├── src/
│   ├── assets/                      # 项目资源(参与构建)
│   │   ├── images/
│   │   └── styles/
│   │       └── global.css
│   ├── components/                  # 公共组件
│   │   ├── MarkdownEditor.vue       # Markdown 编辑器
│   │   ├── TaskList.vue             # 任务列表
│   │   └── PomodoroTimer.vue        # 番茄钟
│   ├── views/                       # 页面组件
│   │   ├── Home.vue                 # 首页
│   │   ├── TaskManager.vue          # 任务管理
│   │   ├── Demo.vue                 # 功能演示
│   │   └── MarkdownEditorView.vue   # 编辑器页面
│   ├── composables/                 # 组合式函数
│   │   ├── useOhos.ts               # OpenHarmony 能力封装
│   │   └── useLocalStorage.ts       # 本地存储
│   ├── services/                    # 服务层
│   │   ├── TaskStore.ts             # 任务状态管理
│   │   ├── EventBus.ts              # 事件总线
│   │   └── PomodoroTimer.ts         # 番茄钟服务
│   ├── types/                       # TypeScript 类型定义
│   │   ├── task.ts                  # 任务相关类型
│   │   └── global.d.ts              # 全局类型声明
│   ├── router/                      # 路由配置
│   │   └── index.ts                 # 路由定义
│   ├── App.vue                      # 根组件
│   └── main.ts                      # 应用入口
├── index.html                       # HTML 模板
├── vite.config.ts                   # Vite 构建配置
├── tsconfig.json                    # TypeScript 配置
└── package.json                     # 依赖配置

这种结构的优势在于:

  • 清晰的模块边界:每个目录有明确的职责
  • 易于扩展:新增功能只需在对应目录下添加文件
  • 便于团队协作:开发者可以快速定位代码位置

四、核心功能实现详解

4.1 Vue3 Composition API 实践

Composition API 是 Vue3 最重要的特性之一,它通过组合式函数(Composables)实现逻辑复用:

vue 复制代码
<script setup lang="ts">
import { ref, computed, watch } from 'vue'

// 定义 Props
interface Props {
  taskId: string
  editable?: boolean
  showMeta?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  editable: true,
  showMeta: false
})

// 定义 Events
const emit = defineEmits<{
  (e: 'update', task: Task): void
  (e: 'delete', taskId: string): void
  (e: 'complete', taskId: string): void
}>()

// 响应式状态
const isEditing = ref(false)
const editContent = ref('')

// 计算属性
const taskStatus = computed(() => {
  return props.editable ? '可编辑' : '只读'
})

// 监听器
watch(() => props.taskId, (newId, oldId) => {
  console.log(`Task ID changed: ${oldId} -> ${newId}`)
  isEditing.value = false
})

// 方法定义
function handleEdit() {
  isEditing.value = true
  editContent.value = getCurrentContent()
}

function handleSave() {
  emit('update', { id: props.taskId, content: editContent.value })
  isEditing.value = false
}
</script>

4.2 Electron IPC 通信机制

Electron 采用多进程架构,主进程和渲染进程之间通过 IPC(Inter-Process Communication)进行通信:

主进程处理(main.ts)

typescript 复制代码
import { ipcMain, dialog, BrowserWindow } from 'electron'
import * as fs from 'fs/promises'
import * as path from 'path'

// 文件选择对话框
ipcMain.handle('dialog:openFile', async (event, options) => {
  const result = await dialog.showOpenDialog({
    properties: ['openFile'],
    filters: [
      { name: 'Markdown', extensions: ['md', 'markdown'] },
      { name: 'Text', extensions: ['txt'] },
      { name: 'All Files', extensions: ['*'] }
    ],
    ...options
  })
  return result.filePaths
})

// 文件读取
ipcMain.handle('fs:readFile', async (event, filePath: string) => {
  try {
    const content = await fs.readFile(filePath, 'utf-8')
    return { success: true, content }
  } catch (error) {
    return { success: false, error: (error as Error).message }
  }
})

// 文件保存
ipcMain.handle('fs:saveFile', async (event, content: string, options) => {
  const result = await dialog.showSaveDialog({
    filters: [
      { name: 'Markdown', extensions: ['md'] },
      { name: 'HTML', extensions: ['html'] }
    ],
    ...options
  })
  
  if (!result.canceled && result.filePath) {
    await fs.writeFile(result.filePath, content, 'utf-8')
    return { success: true, path: result.filePath }
  }
  return { success: false }
})

预加载脚本(preload.ts)

typescript 复制代码
import { contextBridge, ipcRenderer } from 'electron'

contextBridge.exposeInMainWorld('electronAPI', {
  // 文件操作
  openFile: (options?: any) => ipcRenderer.invoke('dialog:openFile', options),
  readFile: (path: string) => ipcRenderer.invoke('fs:readFile', path),
  saveFile: (content: string, options?: any) => ipcRenderer.invoke('fs:saveFile', content, options),
  
  // 系统信息
  getPlatform: () => ipcRenderer.invoke('system:getPlatform'),
  
  // 事件监听
  onNotification: (callback: (data: any) => void) => {
    ipcRenderer.on('notification', (_event, data) => callback(data))
  }
})

渲染进程使用

typescript 复制代码
async function handleOpenFile() {
  const filePaths = await window.electronAPI.openFile()
  
  if (filePaths.length > 0) {
    const result = await window.electronAPI.readFile(filePaths[0])
    
    if (result.success) {
      markdownContent.value = result.content
    } else {
      console.error('读取文件失败:', result.error)
    }
  }
}

4.3 OpenHarmony 原生能力封装

在 OpenHarmony 环境中,通过封装 Composable 函数实现设备能力的优雅调用:

typescript 复制代码
// composables/useOhos.ts
import { ref } from 'vue'

export function useOhos() {
  const isOhosEnv = typeof (window as any).ohos !== 'undefined'
  const systemInfo = ref<any>(null)
  
  const getSystemInfo = async () => {
    if (!isOhosEnv) {
      return { 
        platform: 'browser',
        version: 'N/A',
        deviceType: 'N/A'
      }
    }
    
    try {
      const ohos = (window as any).ohos
      systemInfo.value = {
        platform: 'ohos',
        version: ohos.deviceInfo.sdkApiVersion,
        deviceType: ohos.deviceInfo.deviceType
      }
    } catch (error) {
      console.error('获取系统信息失败:', error)
    }
    
    return systemInfo.value
  }
  
  const showNotification = async (title: string, body: string) => {
    if (!isOhosEnv) {
      // 浏览器环境降级处理
      if ('Notification' in window) {
        new Notification(title, { body })
      } else {
        console.log(`[Notification] ${title}: ${body}`)
      }
      return
    }
    
    // OpenHarmony 通知 API
    try {
      const ohos = (window as any).ohos
      await ohos.notification.publish({
        title,
        body,
        type: 'content'
      })
    } catch (error) {
      console.error('发送通知失败:', error)
    }
  }
  
  const clipboard = {
    read: async () => {
      if (!isOhosEnv) {
        return navigator.clipboard.readText()
      }
      // OpenHarmony 剪贴板 API
      return (window as any).ohos.clipboard.getText()
    },
    write: async (text: string) => {
      if (!isOhosEnv) {
        return navigator.clipboard.writeText(text)
      }
      // OpenHarmony 剪贴板 API
      return (window as any).ohos.clipboard.setText(text)
    }
  }
  
  return {
    isOhosEnv,
    systemInfo,
    getSystemInfo,
    showNotification,
    clipboard
  }
}

这种封装方式实现了优雅降级,确保应用在不同环境下都能正常运行。

五、实战案例:Markdown 编辑器开发

5.1 Markdown 解析与实时预览

Markdown 实时预览的核心是将 Markdown 文本实时转换为 HTML 并渲染。我们使用 markdown-it 作为解析器:

typescript 复制代码
import MarkdownIt from 'markdown-it'

const md = new MarkdownIt({
  html: true,           // 允许 HTML 标签
  linkify: true,        // 自动转换 URL 为链接
  typographer: true,    // 启用排版优化(如引号转换)
  breaks: true          // 将换行符转换为 <br>
})

// 自定义插件:为所有外部链接添加 target="_blank"
md.use(function(md) {
  const defaultRender = md.renderer.rules.link_open || function(tokens, idx, options, env, self) {
    return self.renderToken(tokens, idx, options)
  }

  md.renderer.rules.link_open = function(tokens, idx, options, env, self) {
    const aIndex = tokens[idx].attrIndex('target')
    const hrefIndex = tokens[idx].attrIndex('href')
    
    if (aIndex < 0) {
      tokens[idx].attrPush(['target', '_blank'])
    } else {
      tokens[idx].attrs![aIndex][1] = '_blank'
    }
    
    // 为外部链接添加 rel 属性
    if (hrefIndex >= 0) {
      const href = tokens[idx].attrs![hrefIndex][1]
      if (href.startsWith('http')) {
        const relIndex = tokens[idx].attrIndex('rel')
        if (relIndex < 0) {
          tokens[idx].attrPush(['rel', 'noopener noreferrer'])
        }
      }
    }
    
    return defaultRender(tokens, idx, options, env, self)
  }
})

// 渲染 Markdown
const renderedHtml = md.render(markdownContent)

性能优化:防抖处理

对于大文件,频繁渲染会导致性能问题。使用防抖(debounce)来优化:

typescript 复制代码
import { ref, watch } from 'vue'

const markdownContent = ref('')
const renderedHtml = ref('')

function debounce<T extends (...args: any[]) => any>(
  func: T, 
  wait: number
): (...args: Parameters<T>) => void {
  let timeout: ReturnType<typeof setTimeout> | null = null
  
  return function(...args: Parameters<T>) {
    if (timeout) clearTimeout(timeout)
    timeout = setTimeout(() => func(...args), wait)
  }
}

const debouncedRender = debounce((content: string) => {
  renderedHtml.value = md.render(content)
}, 300)

watch(markdownContent, (newContent) => {
  debouncedRender(newContent)
})

5.2 PDF 导出功能实现

使用 html2pdf.js 库实现 PDF 导出。该库基于 html2canvasjsPDF,可以将 HTML 元素转换为 PDF:

typescript 复制代码
import html2pdf from 'html2pdf.js'

async function exportToPdf(element: HTMLElement, filename: string = 'document.pdf') {
  const opt = {
    margin: [10, 10, 10, 10],           // 页边距(单位:mm)
    filename: filename,
    image: { 
      type: 'jpeg',                     // 图片格式
      quality: 0.98                     // 图片质量
    },
    html2canvas: { 
      scale: 2,                         // 缩放比例,提高文字清晰度
      useCORS: true,                    // 支持跨域图片
      letterRendering: true,            // 改善文字渲染
      logging: false                    // 关闭日志
    },
    jsPDF: { 
      unit: 'mm',                       // 单位
      format: 'a4',                     // 纸张大小
      orientation: 'portrait'           // 方向
    },
    pagebreak: { 
      mode: ['avoid-all', 'css', 'legacy']  // 分页模式
    }
  }
  
  try {
    await html2pdf().set(opt).from(element).save()
    console.log('PDF 导出成功')
  } catch (error) {
    console.error('PDF 导出失败:', error)
    throw error
  }
}

注意事项

  1. 中文支持 :确保使用支持中文的字体,html2canvas 默认使用系统字体
  2. 图片跨域 :设置 useCORS: true,且图片服务器需要配置 CORS 头
  3. 分页控制:使用 CSS 控制分页位置
css 复制代码
/* CSS 分页控制 */
.page-break-after {
  page-break-after: always;
}

.page-break-before {
  page-break-before: always;
}

.avoid-break {
  page-break-inside: avoid;
}

5.3 HTML 导出功能实现

HTML 导出相对简单,将渲染后的 HTML 包装成完整的 HTML 文档:

typescript 复制代码
import { saveAs } from 'file-saver'

function exportToHtml(renderedHtml: string, title: string = 'Markdown Document') {
  const htmlContent = `<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>${title}</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    
    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 
                   'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
      line-height: 1.8;
      color: #24292e;
      max-width: 900px;
      margin: 0 auto;
      padding: 40px 20px;
      background: #ffffff;
    }
    
    h1, h2, h3, h4, h5, h6 {
      margin-top: 24px;
      margin-bottom: 16px;
      font-weight: 600;
      line-height: 1.25;
    }
    
    h1 { font-size: 2em; border-bottom: 2px solid #eaecef; padding-bottom: 0.3em; }
    h2 { font-size: 1.5em; border-bottom: 1px solid #eaecef; padding-bottom: 0.3em; }
    h3 { font-size: 1.25em; }
    h4 { font-size: 1em; }
    
    p { margin: 16px 0; }
    
    code {
      background: #f6f8fa;
      padding: 0.2em 0.4em;
      border-radius: 3px;
      font-size: 0.9em;
      font-family: 'Fira Code', 'Source Code Pro', monospace;
    }
    
    pre {
      background: #f6f8fa;
      padding: 16px;
      overflow: auto;
      border-radius: 6px;
      line-height: 1.45;
    }
    
    pre code {
      background: none;
      padding: 0;
      font-size: 14px;
    }
    
    table {
      border-collapse: collapse;
      width: 100%;
      margin: 16px 0;
    }
    
    th, td {
      border: 1px solid #dfe2e5;
      padding: 8px 12px;
      text-align: left;
    }
    
    th {
      background: #f6f8fa;
      font-weight: 600;
    }
    
    tr:nth-child(2n) {
      background: #f6f8fa;
    }
    
    blockquote {
      border-left: 4px solid #007ACC;
      margin: 16px 0;
      padding: 8px 16px;
      background: #f6f8fa;
      color: #6a737d;
    }
    
    ul, ol {
      padding-left: 2em;
      margin: 16px 0;
    }
    
    li { margin: 4px 0; }
    
    a {
      color: #0366d6;
      text-decoration: none;
    }
    
    a:hover { text-decoration: underline; }
    
    img {
      max-width: 100%;
      height: auto;
      border-radius: 4px;
    }
    
    hr {
      border: 0;
      border-top: 2px solid #eaecef;
      margin: 24px 0;
    }
  </style>
</head>
<body>
${renderedHtml}
</body>
</html>`

  const blob = new Blob([htmlContent], { type: 'text/html;charset=utf-8' })
  saveAs(blob, 'markdown-document.html')
}

六、性能优化与最佳实践

6.1 渲染性能优化

在大型应用中,渲染性能直接影响用户体验。以下是几种常用的优化策略:

虚拟列表优化长列表渲染

vue 复制代码
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useVirtualList } from '@vueuse/core'

const allItems = ref(
  Array.from({ length: 10000 }, (_, i) => ({
    id: i,
    text: `Item ${i}`,
    description: `This is the description for item ${i}`
  }))
)

const { list, containerProps, wrapperProps } = useVirtualList(
  allItems,
  {
    itemHeight: 60,
    overscan: 10
  }
)
</script>

<template>
  <div v-bind="containerProps" style="height: 400px; overflow: auto;">
    <div v-bind="wrapperProps">
      <div 
        v-for="item in list" 
        :key="item.data.id"
        class="list-item"
        style="height: 60px; padding: 10px;"
      >
        <h4>{{ item.data.text }}</h4>
        <p>{{ item.data.description }}</p>
      </div>
    </div>
  </div>
</template>

组件懒加载

typescript 复制代码
import { defineAsyncComponent } from 'vue'

const MarkdownEditor = defineAsyncComponent(() => 
  import('../components/MarkdownEditor.vue')
)

const routes = [
  {
    path: '/editor',
    component: () => import('../views/MarkdownEditorView.vue')
  }
]

计算属性缓存

typescript 复制代码
// 推荐使用计算属性(自动缓存)
const filteredTasks = computed(() => {
  return tasks.value.filter(task => task.status === 'active')
})

// 避免在模板中直接调用方法
// <div v-for="task in getActiveTasks()"> <!-- 每次渲染都会执行 -->

6.2 内存管理与泄漏防护

内存泄漏是桌面应用常见问题,需要特别注意:

定时器清理

typescript 复制代码
import { onMounted, onUnmounted } from 'vue'

let intervalId: number | null = null
let timeoutId: ReturnType<typeof setTimeout> | null = null

onMounted(() => {
  intervalId = window.setInterval(() => {
    console.log('Tick')
  }, 1000)
})

onUnmounted(() => {
  if (intervalId) {
    clearInterval(intervalId)
    intervalId = null
  }
  if (timeoutId) {
    clearTimeout(timeoutId)
    timeoutId = null
  }
})

事件监听清理

typescript 复制代码
import { onMounted, onUnmounted } from 'vue'

function handleResize() {
  console.log('Window resized:', window.innerWidth)
}

onMounted(() => {
  window.addEventListener('resize', handleResize)
})

onUnmounted(() => {
  window.removeEventListener('resize', handleResize)
})

避免闭包引用泄漏

typescript 复制代码
// 问题代码
watch(data, (newVal) => {
  const heavyObject = createHeavyObject()
  setTimeout(() => {
    console.log(heavyObject) // 闭包持有 heavyObject,导致无法回收
  }, 5000)
})

// 解决方案
watch(data, (newVal) => {
  const heavyObject = createHeavyObject()
  const timer = setTimeout(() => {
    console.log(heavyObject)
  }, 5000)
  
  onUnmounted(() => {
    clearTimeout(timer)
    // 清理引用
    heavyObject.cleanup()
  })
})

6.3 打包体积优化

使用 Vite 的代码分割功能优化打包体积:

typescript 复制代码
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['vue', 'vue-router', 'pinia'],
          editor: ['markdown-it'],
          pdf: ['html2pdf.js'],
          utils: ['file-saver']
        }
      }
    },
    chunkSizeWarningLimit: 1000,
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true
      }
    }
  }
})

通过代码分割,首屏加载的 JavaScript 体积可减少 40% 以上。

七、常见问题排查指南

7.1 开发环境配置

问题 1:Node.js 版本不兼容

bash 复制代码
# 推荐使用 Node.js 18+ LTS 版本
# 使用 nvm 管理版本
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
nvm install 18
nvm use 18

# 验证版本
node -v   # 应显示 v18.x.x
npm -v    # 应显示 9.x.x 或更高

问题 2:依赖安装失败

bash 复制代码
# 清除缓存
npm cache clean --force

# 删除后重新安装
rm -rf node_modules package-lock.json
npm install

# 使用国内镜像加速
npm config set registry https://registry.npmmirror.com

7.2 打包部署问题

问题 3:Electron 打包后白屏

typescript 复制代码
// vite.config.ts
export default defineConfig({
  base: './',  // 使用相对路径,重要!
  build: {
    outDir: '../dist'
  }
})

白屏的主要原因:

  1. 使用了绝对路径 base: '/'
  2. 静态资源路径不正确
  3. CSP(内容安全策略)限制

问题 4:OpenHarmony 打包失败

bash 复制代码
# 确保 DevEco Studio 版本正确(推荐 4.0+)
# 检查 SDK 版本
hvigorw --version

# 清理构建缓存
hvigorw clean
hvigorw assembleHap

7.3 跨平台兼容处理

不同平台的路径、换行符等存在差异:

平台 路径分隔符 换行符 注意事项
Windows \ \r\n 注册表、权限
macOS / \n 沙盒权限、签名
Linux / \n 依赖库、权限
OpenHarmony / \n API 限制、沙盒

使用 path 模块处理路径差异:

typescript 复制代码
import path from 'path'

// 正确做法
const filePath = path.join('documents', 'file.md')

// 错误做法(仅适用于 Unix 系统)
const filePath = 'documents/file.md'

八、总结与未来展望

通过本文的系统讲解和实战案例,我们完整掌握了以下核心知识点:

知识模块 核心内容 实战应用
Vue3 开发 Composition API、响应式系统 组件开发、状态管理
Electron 集成 IPC 通信、主进程/渲染进程 文件操作、系统通知
OpenHarmony 适配 JSBridge、设备能力调用 跨平台兼容、优雅降级
Markdown 处理 解析、渲染、导出 实时预览、PDF/HTML 导出
性能优化 虚拟列表、懒加载、内存管理 大文件处理、流畅体验

未来技术展望

  • Vue3.4+:响应式 API 性能进一步提升,开发体验更优
  • Electron 28+:Chromium 120+ 带来更好的 Web 标准支持
  • OpenHarmony 4.0+:更完善的 Web 组件和 JS API 生态
  • Rust + WebAssembly:在高性能计算场景中的应用前景广阔
  • AI 辅助开发:大模型在代码生成、智能提示方面的应用

跨平台开发是一个持续演进的方向,掌握核心技术原理和最佳实践,将帮助我们在技术变革中保持竞争力。


相关推荐
Digitally2 小时前
4 种简单方法将短信从三星传输到华为
华为
想拿大厂offer2 小时前
【Linux】编辑器、IDE 与操作系统:Linux 开发工具链的哲学与实践
linux·ide·编辑器
IntMainJhy2 小时前
Flutter 三方库 photo_view + cached_network_image + video_player 的鸿蒙化适配与实战指南
flutter·华为·harmonyos
其实防守也摸鱼2 小时前
MarkText:开源免费的 Markdown 编辑器新星
笔记·pdf·编辑器·免费·工具·调试·可下载
Highcharts.js2 小时前
实战指南:如何构建一套全平台适配的响应式图表系统?
前端·javascript·highcharts·实战代码·响应式图表
轻口味2 小时前
HarmonyOS 6 轻相机应用开发4:物品分类功能实现
数码相机·分类·harmonyos
77美式2 小时前
手机端键盘弹出导致页面抖动
前端·javascript·uniapp
森叶2 小时前
Electron 实战:utilityProcess 服务脚本热更新、用户目录优先启动与 asar 依赖解析
前端·javascript·electron
深念Y2 小时前
若依框架2026年现状:没被淘汰,反而更强了
前端·javascript·vue.js·框架·系统·模板·若依