Element Plus 跨平台开发实践
学习目标
今天我们将学习如何使用 Element Plus 进行跨平台开发,掌握移动端适配、桌面应用开发、小程序开发和跨平台组件设计。
- 理解跨平台开发的挑战和解决方案
- 掌握移动端适配的最佳实践
- 学习桌面应用开发技术
- 了解小程序开发实践
- 实现跨平台组件设计
1. 跨平台开发架构
1.1 跨平台架构设计
typescript
// packages/cross-platform/src/core/platform-manager.ts
export enum PlatformType {
WEB = 'web',
MOBILE_WEB = 'mobile-web',
DESKTOP = 'desktop',
MOBILE_APP = 'mobile-app',
MINI_PROGRAM = 'mini-program'
}
export interface PlatformCapabilities {
touch: boolean
mouse: boolean
keyboard: boolean
camera: boolean
geolocation: boolean
notification: boolean
fileSystem: boolean
clipboard: boolean
vibration: boolean
orientation: boolean
}
export interface PlatformConfig {
type: PlatformType
capabilities: PlatformCapabilities
viewport: {
width: number
height: number
density: number
}
theme: {
primaryColor: string
darkMode: boolean
customTheme?: Record<string, any>
}
features: {
responsive: boolean
accessibility: boolean
i18n: boolean
offline: boolean
}
}
export interface PlatformAdapter {
name: string
type: PlatformType
detect(): boolean
initialize(config: PlatformConfig): Promise<void>
getCapabilities(): PlatformCapabilities
adaptComponent(component: any, options?: any): any
adaptStyle(style: any, options?: any): any
}
export class PlatformManager {
private adapters = new Map<PlatformType, PlatformAdapter>()
private currentPlatform: PlatformType | null = null
private currentAdapter: PlatformAdapter | null = null
private config: PlatformConfig | null = null
/**
* 注册平台适配器
*/
registerAdapter(adapter: PlatformAdapter): void {
this.adapters.set(adapter.type, adapter)
}
/**
* 检测当前平台
*/
detectPlatform(): PlatformType {
for (const [type, adapter] of this.adapters) {
if (adapter.detect()) {
this.currentPlatform = type
this.currentAdapter = adapter
return type
}
}
// 默认为 web 平台
this.currentPlatform = PlatformType.WEB
this.currentAdapter = this.adapters.get(PlatformType.WEB) || null
return PlatformType.WEB
}
/**
* 初始化平台
*/
async initialize(config: PlatformConfig): Promise<void> {
if (!this.currentAdapter) {
this.detectPlatform()
}
if (!this.currentAdapter) {
throw new Error('No platform adapter found')
}
this.config = config
await this.currentAdapter.initialize(config)
}
/**
* 获取当前平台类型
*/
getCurrentPlatform(): PlatformType | null {
return this.currentPlatform
}
/**
* 获取平台能力
*/
getCapabilities(): PlatformCapabilities | null {
return this.currentAdapter?.getCapabilities() || null
}
/**
* 适配组件
*/
adaptComponent(component: any, options?: any): any {
if (!this.currentAdapter) {
return component
}
return this.currentAdapter.adaptComponent(component, options)
}
/**
* 适配样式
*/
adaptStyle(style: any, options?: any): any {
if (!this.currentAdapter) {
return style
}
return this.currentAdapter.adaptStyle(style, options)
}
/**
* 检查平台能力
*/
hasCapability(capability: keyof PlatformCapabilities): boolean {
const capabilities = this.getCapabilities()
return capabilities ? capabilities[capability] : false
}
/**
* 是否为移动平台
*/
isMobile(): boolean {
return this.currentPlatform === PlatformType.MOBILE_WEB ||
this.currentPlatform === PlatformType.MOBILE_APP
}
/**
* 是否为桌面平台
*/
isDesktop(): boolean {
return this.currentPlatform === PlatformType.DESKTOP ||
this.currentPlatform === PlatformType.WEB
}
/**
* 是否为小程序平台
*/
isMiniProgram(): boolean {
return this.currentPlatform === PlatformType.MINI_PROGRAM
}
}
1.2 Web 平台适配器
typescript
// packages/cross-platform/src/adapters/web-adapter.ts
import type { PlatformAdapter, PlatformCapabilities, PlatformConfig } from '../core/platform-manager'
import { PlatformType } from '../core/platform-manager'
export class WebAdapter implements PlatformAdapter {
name = 'WebAdapter'
type = PlatformType.WEB
/**
* 检测是否为 Web 平台
*/
detect(): boolean {
return typeof window !== 'undefined' &&
typeof document !== 'undefined' &&
!this.isMobile() &&
!this.isMiniProgram()
}
/**
* 初始化 Web 平台
*/
async initialize(config: PlatformConfig): Promise<void> {
// 设置视口
this.setupViewport(config.viewport)
// 设置主题
this.setupTheme(config.theme)
// 设置响应式
if (config.features.responsive) {
this.setupResponsive()
}
// 设置无障碍
if (config.features.accessibility) {
this.setupAccessibility()
}
}
/**
* 获取平台能力
*/
getCapabilities(): PlatformCapabilities {
return {
touch: 'ontouchstart' in window,
mouse: true,
keyboard: true,
camera: navigator.mediaDevices && navigator.mediaDevices.getUserMedia !== undefined,
geolocation: navigator.geolocation !== undefined,
notification: 'Notification' in window,
fileSystem: 'showOpenFilePicker' in window,
clipboard: navigator.clipboard !== undefined,
vibration: 'vibrate' in navigator,
orientation: 'orientation' in screen
}
}
/**
* 适配组件
*/
adaptComponent(component: any, options?: any): any {
// Web 平台通常不需要特殊适配
return component
}
/**
* 适配样式
*/
adaptStyle(style: any, options?: any): any {
return {
...style,
// 添加 Web 特定的样式
userSelect: style.userSelect || 'auto',
cursor: style.cursor || 'default'
}
}
/**
* 设置视口
*/
private setupViewport(viewport: PlatformConfig['viewport']): void {
const meta = document.querySelector('meta[name="viewport"]') as HTMLMetaElement
if (meta) {
meta.content = `width=${viewport.width}, height=${viewport.height}, initial-scale=1.0`
}
}
/**
* 设置主题
*/
private setupTheme(theme: PlatformConfig['theme']): void {
document.documentElement.style.setProperty('--el-color-primary', theme.primaryColor)
if (theme.darkMode) {
document.documentElement.classList.add('dark')
}
if (theme.customTheme) {
Object.entries(theme.customTheme).forEach(([key, value]) => {
document.documentElement.style.setProperty(`--${key}`, value as string)
})
}
}
/**
* 设置响应式
*/
private setupResponsive(): void {
const style = document.createElement('style')
style.textContent = `
@media (max-width: 768px) {
.el-container {
flex-direction: column;
}
.el-aside {
width: 100% !important;
}
}
`
document.head.appendChild(style)
}
/**
* 设置无障碍
*/
private setupAccessibility(): void {
// 添加键盘导航支持
document.addEventListener('keydown', (event) => {
if (event.key === 'Tab') {
document.body.classList.add('keyboard-navigation')
}
})
document.addEventListener('mousedown', () => {
document.body.classList.remove('keyboard-navigation')
})
}
/**
* 检测是否为移动设备
*/
private isMobile(): boolean {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
}
/**
* 检测是否为小程序
*/
private isMiniProgram(): boolean {
return typeof wx !== 'undefined' || typeof my !== 'undefined' || typeof swan !== 'undefined'
}
}
1.3 移动端适配器
typescript
// packages/cross-platform/src/adapters/mobile-adapter.ts
import type { PlatformAdapter, PlatformCapabilities, PlatformConfig } from '../core/platform-manager'
import { PlatformType } from '../core/platform-manager'
export class MobileAdapter implements PlatformAdapter {
name = 'MobileAdapter'
type = PlatformType.MOBILE_WEB
private touchStartY = 0
private touchEndY = 0
/**
* 检测是否为移动端
*/
detect(): boolean {
return typeof window !== 'undefined' &&
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
}
/**
* 初始化移动端
*/
async initialize(config: PlatformConfig): Promise<void> {
// 设置移动端视口
this.setupMobileViewport()
// 设置触摸事件
this.setupTouchEvents()
// 设置移动端主题
this.setupMobileTheme(config.theme)
// 禁用双击缩放
this.disableDoubleTapZoom()
// 设置安全区域
this.setupSafeArea()
}
/**
* 获取移动端能力
*/
getCapabilities(): PlatformCapabilities {
return {
touch: true,
mouse: false,
keyboard: 'ontouchstart' in window,
camera: navigator.mediaDevices && navigator.mediaDevices.getUserMedia !== undefined,
geolocation: navigator.geolocation !== undefined,
notification: 'Notification' in window,
fileSystem: false,
clipboard: navigator.clipboard !== undefined,
vibration: 'vibrate' in navigator,
orientation: true
}
}
/**
* 适配移动端组件
*/
adaptComponent(component: any, options?: any): any {
// 为移动端组件添加触摸优化
return {
...component,
props: {
...component.props,
// 增大触摸目标
size: component.props?.size || 'large',
// 启用触摸反馈
touchFeedback: true
},
style: {
...component.style,
// 最小触摸目标尺寸
minHeight: '44px',
minWidth: '44px',
// 触摸优化
touchAction: 'manipulation'
}
}
}
/**
* 适配移动端样式
*/
adaptStyle(style: any, options?: any): any {
return {
...style,
// 移动端特定样式
WebkitTapHighlightColor: 'transparent',
WebkitTouchCallout: 'none',
WebkitUserSelect: 'none',
touchAction: 'manipulation',
// 字体大小适配
fontSize: this.adaptFontSize(style.fontSize),
// 间距适配
padding: this.adaptSpacing(style.padding),
margin: this.adaptSpacing(style.margin)
}
}
/**
* 设置移动端视口
*/
private setupMobileViewport(): void {
let meta = document.querySelector('meta[name="viewport"]') as HTMLMetaElement
if (!meta) {
meta = document.createElement('meta')
meta.name = 'viewport'
document.head.appendChild(meta)
}
meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover'
}
/**
* 设置触摸事件
*/
private setupTouchEvents(): void {
// 阻止默认的触摸行为
document.addEventListener('touchstart', (e) => {
this.touchStartY = e.touches[0].clientY
}, { passive: true })
document.addEventListener('touchmove', (e) => {
this.touchEndY = e.touches[0].clientY
// 阻止页面滚动(在某些情况下)
const target = e.target as HTMLElement
if (target.classList.contains('no-scroll')) {
e.preventDefault()
}
}, { passive: false })
// 添加触摸反馈
document.addEventListener('touchstart', (e) => {
const target = e.target as HTMLElement
if (target.classList.contains('touch-feedback')) {
target.classList.add('touching')
}
})
document.addEventListener('touchend', (e) => {
const target = e.target as HTMLElement
if (target.classList.contains('touch-feedback')) {
setTimeout(() => {
target.classList.remove('touching')
}, 150)
}
})
}
/**
* 设置移动端主题
*/
private setupMobileTheme(theme: PlatformConfig['theme']): void {
const style = document.createElement('style')
style.textContent = `
:root {
--mobile-safe-area-top: env(safe-area-inset-top);
--mobile-safe-area-bottom: env(safe-area-inset-bottom);
--mobile-safe-area-left: env(safe-area-inset-left);
--mobile-safe-area-right: env(safe-area-inset-right);
}
.touch-feedback.touching {
opacity: 0.7;
transform: scale(0.95);
transition: all 0.15s ease;
}
.mobile-container {
padding-top: var(--mobile-safe-area-top);
padding-bottom: var(--mobile-safe-area-bottom);
padding-left: var(--mobile-safe-area-left);
padding-right: var(--mobile-safe-area-right);
}
`
document.head.appendChild(style)
}
/**
* 禁用双击缩放
*/
private disableDoubleTapZoom(): void {
let lastTouchEnd = 0
document.addEventListener('touchend', (e) => {
const now = Date.now()
if (now - lastTouchEnd <= 300) {
e.preventDefault()
}
lastTouchEnd = now
}, false)
}
/**
* 设置安全区域
*/
private setupSafeArea(): void {
// 检测是否有刘海屏
const hasNotch = window.CSS && CSS.supports('padding-top', 'env(safe-area-inset-top)')
if (hasNotch) {
document.body.classList.add('has-safe-area')
}
}
/**
* 适配字体大小
*/
private adaptFontSize(fontSize?: string): string {
if (!fontSize) return '16px'
const size = parseInt(fontSize)
// 移动端字体稍大一些
return `${Math.max(size + 2, 14)}px`
}
/**
* 适配间距
*/
private adaptSpacing(spacing?: string): string {
if (!spacing) return '0'
const value = parseInt(spacing)
// 移动端间距稍大一些
return `${Math.max(value * 1.2, 8)}px`
}
}
2. 桌面应用开发
2.1 Electron 集成
typescript
// packages/desktop/src/electron-adapter.ts
import { app, BrowserWindow, Menu, ipcMain, dialog, shell } from 'electron'
import type { PlatformAdapter, PlatformCapabilities, PlatformConfig } from '../cross-platform/src/core/platform-manager'
import { PlatformType } from '../cross-platform/src/core/platform-manager'
import path from 'path'
export class ElectronAdapter implements PlatformAdapter {
name = 'ElectronAdapter'
type = PlatformType.DESKTOP
private mainWindow: BrowserWindow | null = null
/**
* 检测是否为 Electron 环境
*/
detect(): boolean {
return typeof process !== 'undefined' &&
process.versions &&
process.versions.electron !== undefined
}
/**
* 初始化 Electron 应用
*/
async initialize(config: PlatformConfig): Promise<void> {
await app.whenReady()
// 创建主窗口
this.createMainWindow(config)
// 设置菜单
this.setupMenu()
// 设置 IPC 通信
this.setupIPC()
// 设置应用事件
this.setupAppEvents()
}
/**
* 获取桌面端能力
*/
getCapabilities(): PlatformCapabilities {
return {
touch: false,
mouse: true,
keyboard: true,
camera: true,
geolocation: false,
notification: true,
fileSystem: true,
clipboard: true,
vibration: false,
orientation: false
}
}
/**
* 适配桌面端组件
*/
adaptComponent(component: any, options?: any): any {
return {
...component,
props: {
...component.props,
// 桌面端默认尺寸
size: component.props?.size || 'default'
},
style: {
...component.style,
// 桌面端特定样式
cursor: 'pointer'
}
}
}
/**
* 适配桌面端样式
*/
adaptStyle(style: any, options?: any): any {
return {
...style,
// 桌面端特定样式
userSelect: 'text',
cursor: style.cursor || 'default'
}
}
/**
* 创建主窗口
*/
private createMainWindow(config: PlatformConfig): void {
this.mainWindow = new BrowserWindow({
width: config.viewport.width || 1200,
height: config.viewport.height || 800,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
enableRemoteModule: false,
preload: path.join(__dirname, 'preload.js')
},
titleBarStyle: 'hiddenInset',
show: false
})
// 加载应用
if (process.env.NODE_ENV === 'development') {
this.mainWindow.loadURL('http://localhost:3000')
this.mainWindow.webContents.openDevTools()
} else {
this.mainWindow.loadFile(path.join(__dirname, '../dist/index.html'))
}
// 窗口准备好后显示
this.mainWindow.once('ready-to-show', () => {
this.mainWindow?.show()
})
// 窗口关闭事件
this.mainWindow.on('closed', () => {
this.mainWindow = null
})
}
/**
* 设置菜单
*/
private setupMenu(): void {
const template = [
{
label: '文件',
submenu: [
{
label: '新建',
accelerator: 'CmdOrCtrl+N',
click: () => {
this.mainWindow?.webContents.send('menu-new')
}
},
{
label: '打开',
accelerator: 'CmdOrCtrl+O',
click: async () => {
const result = await dialog.showOpenDialog(this.mainWindow!, {
properties: ['openFile'],
filters: [
{ name: 'JSON Files', extensions: ['json'] },
{ name: 'All Files', extensions: ['*'] }
]
})
if (!result.canceled) {
this.mainWindow?.webContents.send('menu-open', result.filePaths[0])
}
}
},
{
label: '保存',
accelerator: 'CmdOrCtrl+S',
click: () => {
this.mainWindow?.webContents.send('menu-save')
}
},
{ type: 'separator' },
{
label: '退出',
accelerator: process.platform === 'darwin' ? 'Cmd+Q' : 'Ctrl+Q',
click: () => {
app.quit()
}
}
]
},
{
label: '编辑',
submenu: [
{ role: 'undo', label: '撤销' },
{ role: 'redo', label: '重做' },
{ type: 'separator' },
{ role: 'cut', label: '剪切' },
{ role: 'copy', label: '复制' },
{ role: 'paste', label: '粘贴' }
]
},
{
label: '视图',
submenu: [
{ role: 'reload', label: '重新加载' },
{ role: 'forceReload', label: '强制重新加载' },
{ role: 'toggleDevTools', label: '开发者工具' },
{ type: 'separator' },
{ role: 'resetZoom', label: '实际大小' },
{ role: 'zoomIn', label: '放大' },
{ role: 'zoomOut', label: '缩小' },
{ type: 'separator' },
{ role: 'togglefullscreen', label: '全屏' }
]
},
{
label: '窗口',
submenu: [
{ role: 'minimize', label: '最小化' },
{ role: 'close', label: '关闭' }
]
},
{
label: '帮助',
submenu: [
{
label: '关于',
click: () => {
dialog.showMessageBox(this.mainWindow!, {
type: 'info',
title: '关于',
message: 'Element Plus Desktop App',
detail: '基于 Element Plus 的桌面应用'
})
}
}
]
}
]
const menu = Menu.buildFromTemplate(template as any)
Menu.setApplicationMenu(menu)
}
/**
* 设置 IPC 通信
*/
private setupIPC(): void {
// 文件操作
ipcMain.handle('file:read', async (event, filePath: string) => {
try {
const fs = await import('fs/promises')
const content = await fs.readFile(filePath, 'utf-8')
return { success: true, content }
} catch (error) {
return { success: false, error: (error as Error).message }
}
})
ipcMain.handle('file:write', async (event, filePath: string, content: string) => {
try {
const fs = await import('fs/promises')
await fs.writeFile(filePath, content, 'utf-8')
return { success: true }
} catch (error) {
return { success: false, error: (error as Error).message }
}
})
// 对话框
ipcMain.handle('dialog:showSaveDialog', async (event, options) => {
const result = await dialog.showSaveDialog(this.mainWindow!, options)
return result
})
ipcMain.handle('dialog:showOpenDialog', async (event, options) => {
const result = await dialog.showOpenDialog(this.mainWindow!, options)
return result
})
// 系统操作
ipcMain.handle('system:openExternal', async (event, url: string) => {
await shell.openExternal(url)
})
ipcMain.handle('system:showItemInFolder', async (event, fullPath: string) => {
shell.showItemInFolder(fullPath)
})
// 窗口操作
ipcMain.handle('window:minimize', () => {
this.mainWindow?.minimize()
})
ipcMain.handle('window:maximize', () => {
if (this.mainWindow?.isMaximized()) {
this.mainWindow.unmaximize()
} else {
this.mainWindow?.maximize()
}
})
ipcMain.handle('window:close', () => {
this.mainWindow?.close()
})
}
/**
* 设置应用事件
*/
private setupAppEvents(): void {
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
this.createMainWindow({
type: PlatformType.DESKTOP,
capabilities: this.getCapabilities(),
viewport: { width: 1200, height: 800, density: 1 },
theme: { primaryColor: '#409eff', darkMode: false },
features: { responsive: false, accessibility: true, i18n: true, offline: false }
})
}
})
}
}
2.2 Electron 预加载脚本
typescript
// packages/desktop/src/preload.ts
import { contextBridge, ipcRenderer } from 'electron'
// 暴露安全的 API 给渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
// 文件操作
file: {
read: (filePath: string) => ipcRenderer.invoke('file:read', filePath),
write: (filePath: string, content: string) => ipcRenderer.invoke('file:write', filePath, content)
},
// 对话框
dialog: {
showSaveDialog: (options: any) => ipcRenderer.invoke('dialog:showSaveDialog', options),
showOpenDialog: (options: any) => ipcRenderer.invoke('dialog:showOpenDialog', options)
},
// 系统操作
system: {
openExternal: (url: string) => ipcRenderer.invoke('system:openExternal', url),
showItemInFolder: (fullPath: string) => ipcRenderer.invoke('system:showItemInFolder', fullPath)
},
// 窗口操作
window: {
minimize: () => ipcRenderer.invoke('window:minimize'),
maximize: () => ipcRenderer.invoke('window:maximize'),
close: () => ipcRenderer.invoke('window:close')
},
// 菜单事件监听
onMenuAction: (callback: (action: string, data?: any) => void) => {
ipcRenderer.on('menu-new', () => callback('new'))
ipcRenderer.on('menu-open', (event, filePath) => callback('open', filePath))
ipcRenderer.on('menu-save', () => callback('save'))
}
})
// 类型声明
declare global {
interface Window {
electronAPI: {
file: {
read: (filePath: string) => Promise<{ success: boolean; content?: string; error?: string }>
write: (filePath: string, content: string) => Promise<{ success: boolean; error?: string }>
}
dialog: {
showSaveDialog: (options: any) => Promise<any>
showOpenDialog: (options: any) => Promise<any>
}
system: {
openExternal: (url: string) => Promise<void>
showItemInFolder: (fullPath: string) => Promise<void>
}
window: {
minimize: () => Promise<void>
maximize: () => Promise<void>
close: () => Promise<void>
}
onMenuAction: (callback: (action: string, data?: any) => void) => void
}
}
}
3. 小程序开发实践
3.1 小程序适配器
typescript
// packages/mini-program/src/mini-program-adapter.ts
import type { PlatformAdapter, PlatformCapabilities, PlatformConfig } from '../cross-platform/src/core/platform-manager'
import { PlatformType } from '../cross-platform/src/core/platform-manager'
export class MiniProgramAdapter implements PlatformAdapter {
name = 'MiniProgramAdapter'
type = PlatformType.MINI_PROGRAM
private platform: 'wechat' | 'alipay' | 'baidu' | 'toutiao' | null = null
/**
* 检测是否为小程序环境
*/
detect(): boolean {
if (typeof wx !== 'undefined') {
this.platform = 'wechat'
return true
}
if (typeof my !== 'undefined') {
this.platform = 'alipay'
return true
}
if (typeof swan !== 'undefined') {
this.platform = 'baidu'
return true
}
if (typeof tt !== 'undefined') {
this.platform = 'toutiao'
return true
}
return false
}
/**
* 初始化小程序
*/
async initialize(config: PlatformConfig): Promise<void> {
// 设置小程序主题
this.setupMiniProgramTheme(config.theme)
// 设置导航栏
this.setupNavigationBar(config)
// 设置页面配置
this.setupPageConfig(config)
}
/**
* 获取小程序能力
*/
getCapabilities(): PlatformCapabilities {
const baseCapabilities = {
touch: true,
mouse: false,
keyboard: true,
camera: true,
geolocation: true,
notification: false,
fileSystem: false,
clipboard: false,
vibration: true,
orientation: true
}
// 根据不同平台调整能力
switch (this.platform) {
case 'wechat':
return {
...baseCapabilities,
notification: true,
clipboard: true
}
case 'alipay':
return {
...baseCapabilities,
notification: true
}
default:
return baseCapabilities
}
}
/**
* 适配小程序组件
*/
adaptComponent(component: any, options?: any): any {
// 将 Element Plus 组件转换为小程序组件
return {
...component,
// 小程序特定的属性
props: {
...component.props,
// 小程序组件通常需要更大的触摸目标
size: 'large'
},
// 小程序事件处理
events: this.adaptEvents(component.events || {}),
// 小程序样式
style: this.adaptStyle(component.style || {})
}
}
/**
* 适配小程序样式
*/
adaptStyle(style: any, options?: any): any {
return {
...style,
// 小程序不支持的样式属性需要移除或替换
boxShadow: undefined, // 小程序不支持 box-shadow
// 使用 rpx 单位
fontSize: this.convertToRpx(style.fontSize),
padding: this.convertToRpx(style.padding),
margin: this.convertToRpx(style.margin),
width: this.convertToRpx(style.width),
height: this.convertToRpx(style.height)
}
}
/**
* 设置小程序主题
*/
private setupMiniProgramTheme(theme: PlatformConfig['theme']): void {
// 小程序主题设置通常在 app.json 中配置
const themeConfig = {
navigationBarBackgroundColor: theme.primaryColor,
navigationBarTextStyle: theme.darkMode ? 'white' : 'black'
}
// 这里只是示例,实际需要通过小程序 API 设置
console.log('Mini program theme config:', themeConfig)
}
/**
* 设置导航栏
*/
private setupNavigationBar(config: PlatformConfig): void {
if (this.platform === 'wechat' && typeof wx !== 'undefined') {
wx.setNavigationBarColor({
frontColor: config.theme.darkMode ? '#ffffff' : '#000000',
backgroundColor: config.theme.primaryColor
})
}
}
/**
* 设置页面配置
*/
private setupPageConfig(config: PlatformConfig): void {
// 小程序页面配置
const pageConfig = {
enablePullDownRefresh: true,
onReachBottomDistance: 50,
backgroundTextStyle: config.theme.darkMode ? 'dark' : 'light'
}
console.log('Mini program page config:', pageConfig)
}
/**
* 适配事件
*/
private adaptEvents(events: Record<string, Function>): Record<string, Function> {
const adaptedEvents: Record<string, Function> = {}
Object.entries(events).forEach(([eventName, handler]) => {
// 将 Web 事件名转换为小程序事件名
const miniProgramEventName = this.convertEventName(eventName)
adaptedEvents[miniProgramEventName] = handler
})
return adaptedEvents
}
/**
* 转换事件名
*/
private convertEventName(eventName: string): string {
const eventMap: Record<string, string> = {
'click': 'tap',
'mouseenter': 'touchstart',
'mouseleave': 'touchend',
'input': 'input',
'change': 'change'
}
return eventMap[eventName] || eventName
}
/**
* 转换为 rpx 单位
*/
private convertToRpx(value?: string): string | undefined {
if (!value) return undefined
// 如果已经是 rpx 单位,直接返回
if (value.includes('rpx')) return value
// 将 px 转换为 rpx (假设设计稿宽度为 750px)
const pxValue = parseInt(value)
if (isNaN(pxValue)) return value
return `${pxValue * 2}rpx`
}
/**
* 获取小程序平台信息
*/
getPlatformInfo(): any {
switch (this.platform) {
case 'wechat':
return typeof wx !== 'undefined' ? wx.getSystemInfoSync() : null
case 'alipay':
return typeof my !== 'undefined' ? my.getSystemInfoSync() : null
case 'baidu':
return typeof swan !== 'undefined' ? swan.getSystemInfoSync() : null
case 'toutiao':
return typeof tt !== 'undefined' ? tt.getSystemInfoSync() : null
default:
return null
}
}
}
3.2 小程序组件转换器
typescript
// packages/mini-program/src/component-converter.ts
export interface MiniProgramComponent {
template: string
script: string
style: string
config: any
}
export class ComponentConverter {
/**
* 将 Vue 组件转换为小程序组件
*/
convertToMiniProgram(vueComponent: any, platform: 'wechat' | 'alipay' | 'baidu' | 'toutiao'): MiniProgramComponent {
const template = this.convertTemplate(vueComponent.template, platform)
const script = this.convertScript(vueComponent.script, platform)
const style = this.convertStyle(vueComponent.style, platform)
const config = this.generateConfig(vueComponent, platform)
return {
template,
script,
style,
config
}
}
/**
* 转换模板
*/
private convertTemplate(template: string, platform: string): string {
let converted = template
// 转换指令
converted = converted.replace(/v-if="([^"]+)"/g, 'wx:if="{{$1}}"')
converted = converted.replace(/v-for="([^"]+) in ([^"]+)"/g, 'wx:for="{{$2}}" wx:for-item="$1"')
converted = converted.replace(/v-show="([^"]+)"/g, 'hidden="{{!($1)}}"')
// 转换事件
converted = converted.replace(/@click="([^"]+)"/g, 'bindtap="$1"')
converted = converted.replace(/@input="([^"]+)"/g, 'bindinput="$1"')
converted = converted.replace(/@change="([^"]+)"/g, 'bindchange="$1"')
// 转换插值
converted = converted.replace(/{{\s*([^}]+)\s*}}/g, '{{$1}}')
// 转换组件名
converted = this.convertComponentNames(converted, platform)
return converted
}
/**
* 转换脚本
*/
private convertScript(script: string, platform: string): string {
// 这里需要将 Vue 的 Composition API 转换为小程序的 Page/Component 语法
// 这是一个复杂的转换过程,这里只是示例
let converted = `
Component({
properties: {
// 组件属性
},
data: {
// 组件数据
},
methods: {
// 组件方法
},
lifetimes: {
attached() {
// 组件生命周期
}
}
})
`
return converted
}
/**
* 转换样式
*/
private convertStyle(style: string, platform: string): string {
let converted = style
// 移除不支持的 CSS 属性
converted = converted.replace(/box-shadow:[^;]+;/g, '')
converted = converted.replace(/filter:[^;]+;/g, '')
// 转换单位
converted = converted.replace(/(\d+)px/g, (match, num) => {
return `${parseInt(num) * 2}rpx`
})
return converted
}
/**
* 生成配置
*/
private generateConfig(vueComponent: any, platform: string): any {
const config: any = {
component: true
}
// 根据平台添加特定配置
switch (platform) {
case 'wechat':
config.styleIsolation = 'isolated'
break
case 'alipay':
config.styleIsolation = 'apply-shared'
break
}
return config
}
/**
* 转换组件名
*/
private convertComponentNames(template: string, platform: string): string {
const componentMap: Record<string, string> = {
'el-button': 'button',
'el-input': 'input',
'el-image': 'image',
'el-text': 'text'
}
let converted = template
Object.entries(componentMap).forEach(([vueComponent, miniComponent]) => {
const regex = new RegExp(`<${vueComponent}`, 'g')
converted = converted.replace(regex, `<${miniComponent}`)
const closeRegex = new RegExp(`</${vueComponent}>`, 'g')
converted = converted.replace(closeRegex, `</${miniComponent}>`)
})
return converted
}
}
4. 实践练习
练习1:平台检测
typescript
// 实现平台检测和适配
// 1. 创建平台检测器
// 2. 实现自动适配逻辑
// 3. 添加平台特性检测
// 4. 实现优雅降级
练习2:移动端优化
vue
<!-- 实现移动端优化 -->
<!-- 1. 响应式布局设计 -->
<!-- 2. 触摸交互优化 -->
<!-- 3. 性能优化策略 -->
<!-- 4. 移动端特效实现 -->
练习3:桌面应用
typescript
// 开发桌面应用
// 1. Electron 应用搭建
// 2. 原生功能集成
// 3. 菜单和快捷键
// 4. 文件系统操作
练习4:小程序开发
typescript
// 小程序开发实践
// 1. 组件转换实现
// 2. API 适配层开发
// 3. 样式兼容处理
// 4. 性能优化策略
学习资源
跨平台开发
移动端开发
小程序开发
作业
- 平台适配器:实现完整的平台检测和适配系统
- 移动端应用:开发一个移动端优化的 Element Plus 应用
- 桌面应用:使用 Electron 创建一个桌面应用
- 小程序应用:将 Element Plus 组件转换为小程序组件
- 跨平台组件:设计一套跨平台的组件库
下一步学习
明天我们将学习「Element Plus 社区贡献与开源实践」,包括:
- 开源项目贡献流程
- 代码规范和质量保证
- 文档编写和维护
- 社区建设和管理
- 版本发布和维护
总结
今天我们深入学习了 Element Plus 的跨平台开发实践:
- 跨平台架构:设计了完整的平台管理和适配系统
- 移动端适配:实现了移动端的触摸优化和响应式设计
- 桌面应用开发:学习了 Electron 集成和原生功能调用
- 小程序开发:掌握了小程序适配和组件转换技术
- 平台特性适配:了解了不同平台的能力和限制
通过这些学习,你现在能够:
- 设计跨平台的应用架构
- 实现移动端的优化和适配
- 开发 Electron 桌面应用
- 适配小程序平台
- 处理平台差异和兼容性问题