Platform Adaptation
uni-app is a front-end framework that uses Vue.js to develop cross-platform applications, capable of running simultaneously on iOS, Android, H5, and various mini-program platforms. This guide will detail uni-app's cross-platform adaptation strategies and best practices to help developers efficiently develop for multiple platforms.
Conditional Compilation
Conditional compilation is an important mechanism in uni-app for handling platform differences, allowing different code to be compiled for different platforms.
Conditional Compilation Syntax
1. Using #ifdef and #ifndef for Conditional Compilation
// #ifdef Compile only on certain platforms
// #ifdef APP-PLUS
console.log('This code only compiles on App platform');
// #endif
// #ifndef Compile on all platforms except certain ones
// #ifndef MP-WEIXIN
console.log('This code will not compile on WeChat mini-program platform');
// #endif
2. Supported Platforms
Value | Platform |
---|---|
APP-PLUS | App |
APP-PLUS-NVUE | App nvue |
H5 | H5 |
MP-WEIXIN | WeChat Mini Program |
MP-ALIPAY | Alipay Mini Program |
MP-BAIDU | Baidu Mini Program |
MP-TOUTIAO | ByteDance Mini Program |
MP-QQ | QQ Mini Program |
MP-KUAISHOU | Kuaishou Mini Program |
MP | All Mini Programs |
QUICKAPP-WEBVIEW | QuickApp Universal |
QUICKAPP-WEBVIEW-UNION | QuickApp Union |
QUICKAPP-WEBVIEW-HUAWEI | QuickApp Huawei |
3. Multi-platform Conditional Compilation
// #ifdef APP-PLUS || H5
console.log('This code only compiles on App and H5 platforms');
// #endif
Conditional Compilation Use Cases
1. Using Conditional Compilation in Template
<template>
<view>
<!-- #ifdef APP-PLUS -->
<view>This is an App-specific component</view>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<view>This is a WeChat mini-program specific component</view>
<!-- #endif -->
<!-- #ifdef H5 -->
<view>This is an H5-specific component</view>
<!-- #endif -->
<!-- Content displayed on all platforms -->
<view>This content is displayed on all platforms</view>
</view>
</template>
2. Using Conditional Compilation in Script
<script>
export default {
data() {
return {
platformInfo: ''
}
},
onLoad() {
// #ifdef APP-PLUS
this.platformInfo = 'Current platform is App';
// Call App-specific API
const currentWebview = this.$scope.$getAppWebview();
// #endif
// #ifdef MP-WEIXIN
this.platformInfo = 'Current platform is WeChat Mini Program';
// Call WeChat mini-program specific API
wx.getSystemInfo({
success: (res) => {
console.log(res);
}
});
// #endif
// #ifdef H5
this.platformInfo = 'Current platform is H5';
// Call H5-specific API
document.addEventListener('click', () => {
console.log('H5 click event');
});
// #endif
},
methods: {
// #ifdef APP-PLUS
appMethod() {
// App platform specific method
}
// #endif
}
}
</script>
3. Using Conditional Compilation in Style
<style>
/* Universal styles for all platforms */
.content {
padding: 15px;
}
/* #ifdef APP-PLUS */
/* App platform specific styles */
.app-content {
background-color: #007AFF;
}
/* #endif */
/* #ifdef MP-WEIXIN */
/* WeChat mini-program platform specific styles */
.mp-content {
background-color: #04BE02;
}
/* #endif */
/* #ifdef H5 */
/* H5 platform specific styles */
.h5-content {
background-color: #FC0;
}
/* #endif */
</style>
4. Whole File Conditional Compilation
You can apply conditional compilation to entire files, such as creating platform-specific pages or components:
- Platform-specific pages: pages/login/login.app.vue (only compiled in App)
- Platform-specific components: components/ad/ad.mp.vue (only compiled in mini-programs)
5. Conditional Compilation in Static Directory
Files in the static directory are not compiled, but conditional compilation can be achieved through directory names:
┌─static
│ ├─app-plus # Only effective on app platform
│ │ └─logo.png
│ ├─h5 # Only effective on H5 platform
│ │ └─logo.png
│ ├─mp-weixin # Only effective on WeChat mini-program platform
│ │ └─logo.png
│ └─logo.png # Effective on all platforms
Conditional Compilation Best Practices
1. Extract Platform-Specific Code
Extract platform-specific code into separate files and import them through conditional compilation:
// platform.js
// #ifdef APP-PLUS
export const platform = {
name: 'APP',
// App platform specific methods and properties
share: function(options) {
uni.share({
provider: 'weixin',
...options
});
}
};
// #endif
// #ifdef MP-WEIXIN
export const platform = {
name: 'MP-WEIXIN',
// WeChat mini-program platform specific methods and properties
share: function(options) {
wx.showShareMenu({
withShareTicket: true,
...options
});
}
};
// #endif
// #ifdef H5
export const platform = {
name: 'H5',
// H5 platform specific methods and properties
share: function(options) {
// H5 sharing logic
}
};
// #endif
Then call uniformly in business code:
import { platform } from './platform.js';
// Call platform-specific sharing method
platform.share({
title: 'Share Title',
content: 'Share Content'
});
2. Use Unified API Encapsulation
Provide unified API encapsulation for different platform features:
// api.js
/**
* Get location information
* @returns {Promise} Location information Promise
*/
export function getLocation() {
return new Promise((resolve, reject) => {
// #ifdef APP-PLUS
plus.geolocation.getCurrentPosition(
(position) => {
resolve({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy
});
},
(error) => {
reject(error);
},
{ timeout: 10000 }
);
// #endif
// #ifdef MP-WEIXIN
wx.getLocation({
type: 'gcj02',
success: (res) => {
resolve({
latitude: res.latitude,
longitude: res.longitude,
accuracy: res.accuracy
});
},
fail: (err) => {
reject(err);
}
});
// #endif
// #ifdef H5
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
resolve({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy
});
},
(error) => {
reject(error);
},
{ timeout: 10000 }
);
} else {
reject(new Error('Browser does not support Geolocation API'));
}
// #endif
});
}
3. Avoid Overusing Conditional Compilation
Overusing conditional compilation can make code difficult to maintain. You should minimize the use of conditional compilation and prioritize the following approaches:
- Use cross-platform APIs provided by uni-app
- Abstract platform differences and provide unified interfaces
- Use component-based thinking to encapsulate platform-specific functionality as components
Style Adaptation
1. Screen Adaptation
uni-app supports adaptive units based on 750rpx screen width, achieving consistent layout effects on screens of different sizes.
.container {
width: 750rpx; /* Full screen width */
padding: 30rpx; /* Padding */
}
.card {
width: 690rpx; /* 750rpx - 30rpx * 2 */
height: 300rpx;
margin-bottom: 20rpx;
}
2. Style Compatibility Handling
Different platforms have varying levels of CSS support, requiring attention to style compatibility:
/* Use flex layout, good compatibility */
.flex-container {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
/* Avoid selectors not supported by mini-programs */
/* Not recommended: .parent > .child */
/* Recommended: */
.parent-child {
color: #333;
}
/* Use conditional compilation for platform-specific styles */
/* #ifdef H5 */
.special-style {
transition: all 0.3s;
}
/* #endif */
3. Safe Area Adaptation
For full-screen phones, safe area handling is needed:
/* Page root element */
.page {
padding-bottom: constant(safe-area-inset-bottom); /* iOS 11.0 */
padding-bottom: env(safe-area-inset-bottom); /* iOS 11.2+ */
padding-top: constant(safe-area-inset-top);
padding-top: env(safe-area-inset-top);
}
/* Bottom fixed navigation bar */
.footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: calc(100rpx + constant(safe-area-inset-bottom));
height: calc(100rpx + env(safe-area-inset-bottom));
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
4. Dark Mode Adaptation
Support system dark mode:
/* Define variables */
page {
/* Light mode variables */
--bg-color: #ffffff;
--text-color: #333333;
--border-color: #eeeeee;
}
/* Dark mode variables */
@media (prefers-color-scheme: dark) {
page {
--bg-color: #1a1a1a;
--text-color: #f2f2f2;
--border-color: #333333;
}
}
/* Use variables */
.container {
background-color: var(--bg-color);
color: var(--text-color);
border: 1px solid var(--border-color);
}
Feature Adaptation
1. Navigation Bar Adaptation
Different platforms have different navigation bar behaviors, requiring adaptation:
<template>
<view class="page">
<!-- Custom navigation bar -->
<!-- #ifdef APP-PLUS || H5 -->
<view class="custom-nav" :style="{ height: navHeight + 'px', paddingTop: statusBarHeight + 'px' }">
<view class="nav-content">
<view class="back" @click="goBack">
<text class="iconfont icon-back"></text>
</view>
<view class="title">{{ title }}</view>
<view class="placeholder"></view>
</view>
</view>
<!-- #endif -->
<!-- Page content, adjust padding based on platform -->
<view class="content" :style="contentStyle">
<!-- Page content -->
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'Page Title',
statusBarHeight: 0,
navHeight: 0
}
},
computed: {
contentStyle() {
let style = {};
// #ifdef APP-PLUS || H5
style.paddingTop = this.navHeight + 'px';
// #endif
// #ifdef MP
// Mini-programs use native navigation bar, no extra padding needed
// #endif
return style;
}
},
onLoad() {
this.initNavBar();
},
methods: {
initNavBar() {
const systemInfo = uni.getSystemInfoSync();
this.statusBarHeight = systemInfo.statusBarHeight;
// #ifdef APP-PLUS || H5
// App and H5 use custom navigation bar
this.navHeight = this.statusBarHeight + 44; // Status bar height + navigation bar height
// #endif
// #ifdef MP-WEIXIN
// Set mini-program native navigation bar
uni.setNavigationBarTitle({
title: this.title
});
// #endif
},
goBack() {
uni.navigateBack({
delta: 1
});
}
}
}
</script>
<style>
.page {
position: relative;
}
/* Custom navigation bar */
.custom-nav {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
background-color: #ffffff;
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.1);
}
.nav-content {
display: flex;
height: 44px;
align-items: center;
justify-content: space-between;
padding: 0 15px;
}
.back {
width: 30px;
height: 30px;
display: flex;
align-items: center;
}
.title {
font-size: 18px;
font-weight: 500;
}
.placeholder {
width: 30px;
}
/* Content area */
.content {
width: 100%;
}
</style>
2. Bottom Safe Area Adaptation
For devices with bottom safe areas like iPhone X:
<template>
<view class="page">
<!-- Page content -->
<view class="content">
<!-- Content -->
</view>
<!-- Bottom navigation bar -->
<view class="footer safe-area-bottom">
<view class="tab-item" v-for="(item, index) in tabs" :key="index" @click="switchTab(index)">
<view class="icon">
<text class="iconfont" :class="currentTab === index ? item.activeIcon : item.icon"></text>
</view>
<view class="text" :class="{ active: currentTab === index }">{{ item.text }}</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
currentTab: 0,
tabs: [
{ text: 'Home', icon: 'icon-home', activeIcon: 'icon-home-fill' },
{ text: 'Category', icon: 'icon-category', activeIcon: 'icon-category-fill' },
{ text: 'Cart', icon: 'icon-cart', activeIcon: 'icon-cart-fill' },
{ text: 'Profile', icon: 'icon-user', activeIcon: 'icon-user-fill' }
]
}
},
methods: {
switchTab(index) {
this.currentTab = index;
}
}
}
</script>
<style>
.page {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.content {
flex: 1;
padding-bottom: 50px; /* Bottom navigation bar height */
}
.footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 50px;
background-color: #ffffff;
display: flex;
box-shadow: 0 -1px 5px rgba(0, 0, 0, 0.1);
}
.safe-area-bottom {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.tab-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.icon {
font-size: 24px;
color: #999;
}
.text {
font-size: 12px;
color: #999;
margin-top: 2px;
}
.text.active, .tab-item.active .icon {
color: #007AFF;
}
</style>
3. Keyboard Occlusion Adaptation
Handle page adaptation when keyboard appears:
<template>
<view class="page">
<form @submit="handleSubmit">
<view class="form-item">
<input class="input" type="text" v-model="username" placeholder="Username" />
</view>
<view class="form-item">
<input class="input" type="password" v-model="password" placeholder="Password" />
</view>
<view class="form-item">
<button class="submit-btn" form-type="submit">Login</button>
</view>
</form>
</view>
</template>
<script>
export default {
data() {
return {
username: '',
password: '',
keyboardHeight: 0
}
},
onLoad() {
// #ifdef APP-PLUS
// Listen to keyboard height changes
plus.key.addEventListener('showkeyboard', (e) => {
this.keyboardHeight = e.height;
});
plus.key.addEventListener('hidekeyboard', () => {
this.keyboardHeight = 0;
});
// #endif
},
methods: {
handleSubmit() {
// Handle form submission
console.log('Submit form', this.username, this.password);
// Hide keyboard
uni.hideKeyboard();
}
}
}
</script>
<style>
.page {
padding: 30rpx;
}
.form-item {
margin-bottom: 30rpx;
}
.input {
height: 90rpx;
border: 1px solid #ddd;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 28rpx;
}
.submit-btn {
background-color: #007AFF;
color: #fff;
height: 90rpx;
line-height: 90rpx;
border-radius: 8rpx;
font-size: 32rpx;
}
/* #ifdef APP-PLUS */
/* Handle page scrolling when keyboard appears */
.page {
padding-bottom: 300rpx;
}
/* #endif */
</style>
Platform-Specific Feature Adaptation
1. Share Feature Adaptation
Different platforms have different sharing implementations:
// share.js
/**
* Universal sharing method
* @param {Object} options Share parameters
* @returns {Promise} Share result Promise
*/
export function shareContent(options) {
return new Promise((resolve, reject) => {
// #ifdef APP-PLUS
// App platform sharing
uni.share({
provider: options.provider || 'weixin', // Share service provider
scene: options.scene || 'WXSceneSession', // Scene
type: options.type || 0, // Share type
title: options.title || '', // Title
summary: options.summary || '', // Summary
imageUrl: options.imageUrl || '', // Image URL
href: options.href || '', // Jump link
success: (res) => {
resolve(res);
},
fail: (err) => {
reject(err);
}
});
// #endif
// #ifdef MP-WEIXIN
// WeChat mini-program sharing
// Note: WeChat mini-program sharing needs to be handled in page's onShareAppMessage lifecycle
// Here only return share parameters
resolve({
title: options.title || '',
path: options.path || '/pages/index/index',
imageUrl: options.imageUrl || ''
});
// #endif
// #ifdef H5
// H5 platform sharing
if (navigator.share && options.type === 0) {
// Use Web Share API
navigator.share({
title: options.title || '',
text: options.summary || '',
url: options.href || window.location.href
}).then(() => {
resolve({ success: true });
}).catch((err) => {
reject(err);
});
} else {
// Copy link
const input = document.createElement('input');
input.setAttribute('readonly', 'readonly');
input.setAttribute('value', options.href || window.location.href);
document.body.appendChild(input);
input.select();
document.execCommand('copy');
document.body.removeChild(input);
uni.showToast({
title: 'Link copied, please paste and send to friends',
icon: 'none'
});
resolve({ success: true });
}
// #endif
});
}
2. Payment Feature Adaptation
Different platforms have different payment implementations:
// payment.js
/**
* Universal payment method
* @param {Object} options Payment parameters
* @returns {Promise} Payment result Promise
*/
export function payment(options) {
return new Promise((resolve, reject) => {
// #ifdef APP-PLUS
// App platform payment
uni.requestPayment({
provider: options.provider || 'alipay', // Payment provider
orderInfo: options.orderInfo, // Order data
success: (res) => {
resolve(res);
},
fail: (err) => {
reject(err);
}
});
// #endif
// #ifdef MP-WEIXIN
// WeChat mini-program payment
uni.requestPayment({
timeStamp: options.timeStamp,
nonceStr: options.nonceStr,
package: options.package,
signType: options.signType,
paySign: options.paySign,
success: (res) => {
resolve(res);
},
fail: (err) => {
reject(err);
}
});
// #endif
// #ifdef MP-ALIPAY
// Alipay mini-program payment
uni.requestPayment({
tradeNO: options.tradeNO,
success: (res) => {
resolve(res);
},
fail: (err) => {
reject(err);
}
});
// #endif
// #ifdef H5
// H5 platform payment
// H5 platform usually redirects to payment page
if (options.payUrl) {
window.location.href = options.payUrl;
resolve({ success: true });
} else {
reject(new Error('Missing payment URL'));
}
// #endif
});
}
Performance Optimization
1. First Screen Loading Optimization
Different platform first screen loading optimization strategies:
// app.vue
<script>
export default {
onLaunch: function() {
// Preload data
this.preloadData();
// Preload pages
this.preloadPages();
},
methods: {
preloadData() {
// #ifdef APP-PLUS
// App platform can preload data at startup
uni.request({
url: 'https://api.example.com/config',
success: (res) => {
// Cache global configuration
getApp().globalData.config = res.data;
}
});
// #endif
// #ifdef MP
// Mini-program platform can use periodic updates
const lastUpdateTime = uni.getStorageSync('lastUpdateTime') || 0;
const now = Date.now();
// Update only after 1 hour
if (now - lastUpdateTime > 3600000) {
uni.request({
url: 'https://api.example.com/config',
success: (res) => {
// Cache global configuration
getApp().globalData.config = res.data;
// Update time
uni.setStorageSync('lastUpdateTime', now);
}
});
} else {
// Use cached data
getApp().globalData.config = uni.getStorageSync('config');
}
// #endif
},
preloadPages() {
// #ifdef APP-PLUS
// App platform can preload pages
uni.preloadPage({
url: '/pages/index/index'
});
uni.preloadPage({
url: '/pages/user/user'
});
// #endif
}
}
}
</script>
2. Image Optimization
Image optimization for different platforms:
<template>
<view class="container">
<!-- Lazy load images -->
<image
class="lazy-image"
:src="imageSrc"
:lazy-load="true"
@load="onImageLoad"
@error="onImageError"
></image>
<!-- Load different sized images based on platform -->
<image class="responsive-image" :src="responsiveImageSrc"></image>
</view>
</template>
<script>
export default {
data() {
return {
imageSrc: '',
placeholderImage: '/static/placeholder.png',
imageUrl: 'https://example.com/image.jpg',
imageLoaded: false
}
},
computed: {
responsiveImageSrc() {
const systemInfo = uni.getSystemInfoSync();
const pixelRatio = systemInfo.pixelRatio || 2;
// Choose different sized images based on device pixel ratio
return `https://example.com/image_${pixelRatio}x.jpg`;
}
},
onLoad() {
// Show placeholder first
this.imageSrc = this.placeholderImage;
// Preload actual image
this.preloadImage(this.imageUrl);
},
methods: {
preloadImage(url) {
// #ifdef APP-PLUS || H5
// App and H5 platforms can use Image object for preloading
const image = new Image();
image.onload = () => {
// Replace after image loading is complete
this.imageSrc = url;
this.imageLoaded = true;
};
image.onerror = () => {
console.error('Image loading failed:', url);
// Keep placeholder
};
image.src = url;
// #endif
// #ifdef MP
// Mini-program platform directly set src
this.imageSrc = url;
// #endif
},
onImageLoad(e) {
console.log('Image loaded successfully:', e);
this.imageLoaded = true;
},
onImageError(e) {
console.error('Image loading failed:', e);
// Show placeholder
this.imageSrc = this.placeholderImage;
}
}
}
</script>
Debugging and Testing
1. Cross-platform Debugging
Debugging methods for different platforms:
// debug.js
/**
* Cross-platform logging tool
*/
class Logger {
constructor(options = {}) {
this.enabled = options.enabled !== false;
this.level = options.level || 'info';
this.tag = options.tag || 'App';
// Log levels
this.levels = {
debug: 0,
info: 1,
warn: 2,
error: 3
};
}
/**
* Determine if log should be output
* @param {string} level Log level
* @returns {boolean} Whether to output
*/
shouldLog(level) {
return this.enabled && this.levels[level] >= this.levels[this.level];
}
/**
* Format log
* @param {string} level Log level
* @param {Array} args Log arguments
* @returns {Array} Formatted arguments
*/
formatLog(level, args) {
const time = new Date().toISOString();
const prefix = `[${this.tag}] [${level.toUpperCase()}] [${time}]`;
return [prefix, ...args];
}
/**
* Output debug log
*/
debug(...args) {
if (this.shouldLog('debug')) {
console.log(...this.formatLog('debug', args));
}
}
/**
* Output info log
*/
info(...args) {
if (this.shouldLog('info')) {
console.info(...this.formatLog('info', args));
}
}
/**
* Output warning log
*/
warn(...args) {
if (this.shouldLog('warn')) {
console.warn(...this.formatLog('warn', args));
}
}
/**
* Output error log
*/
error(...args) {
if (this.shouldLog('error')) {
console.error(...this.formatLog('error', args));
}
}
}
// Create logger instance
const logger = new Logger({
enabled: process.env.NODE_ENV !== 'production',
level: 'debug',
tag: 'UniApp'
});
export default logger;
2. Cross-platform Testing
Testing methods for different platforms:
// test-utils.js
/**
* Cross-platform testing utilities
*/
class TestUtils {
/**
* Get current platform
* @returns {string} Platform identifier
*/
static getPlatform() {
// #ifdef APP-PLUS
return 'APP';
// #endif
// #ifdef H5
return 'H5';
// #endif
// #ifdef MP-WEIXIN
return 'MP-WEIXIN';
// #endif
// #ifdef MP-ALIPAY
return 'MP-ALIPAY';
// #endif
return 'UNKNOWN';
}
/**
* Check if API is available
* @param {string} apiName API name
* @returns {boolean} Whether available
*/
static isApiAvailable(apiName) {
if (!uni[apiName]) {
return false;
}
// Special API checks
if (apiName === 'share' && !this.isApp()) {
return false;
}
if (apiName === 'requestPayment' && this.isH5()) {
return false;
}
return true;
}
/**
* Safe API call
* @param {string} apiName API name
* @param {Object} params Parameters
* @returns {Promise} API call result
*/
static safeApiCall(apiName, params = {}) {
return new Promise((resolve, reject) => {
if (!this.isApiAvailable(apiName)) {
reject(new Error(`API ${apiName} is not available on current platform`));
return;
}
uni[apiName]({
...params,
success: (res) => {
resolve(res);
},
fail: (err) => {
reject(new Error(err.errMsg || `${apiName} failed`));
}
});
});
}
/**
* Check if current platform is App
* @returns {boolean} Whether is App platform
*/
static isApp() {
return this.getPlatform() === 'APP';
}
/**
* Check if current platform is H5
* @returns {boolean} Whether is H5 platform
*/
static isH5() {
return this.getPlatform() === 'H5';
}
/**
* Check if current platform is Mini Program
* @returns {boolean} Whether is Mini Program platform
*/
static isMiniProgram() {
const platform = this.getPlatform();
return platform.startsWith('MP-');
}
/**
* Mock API for testing
* @param {string} apiName API name
* @param {Function} mockFn Mock function
*/
static mockApi(apiName, mockFn) {
const originalApi = uni[apiName];
uni[apiName] = mockFn;
// Return restore function
return () => {
uni[apiName] = originalApi;
};
}
}
export default TestUtils;
Best Practices
1. Code Organization
Organize platform-specific code systematically:
src/
├── common/ # Common code for all platforms
│ ├── utils/
│ ├── components/
│ └── styles/
├── platforms/ # Platform-specific code
│ ├── app/ # App platform specific
│ ├── h5/ # H5 platform specific
│ └── mp/ # Mini Program specific
└── pages/ # Page files
2. Configuration Management
Use configuration files to manage platform differences:
// config/platform.js
const platformConfig = {
// #ifdef APP-PLUS
platform: 'app',
apiBaseUrl: 'https://api.example.com/app',
features: {
share: true,
payment: true,
camera: true,
location: true
}
// #endif
// #ifdef H5
platform: 'h5',
apiBaseUrl: 'https://api.example.com/h5',
features: {
share: false,
payment: false,
camera: false,
location: true
}
// #endif
// #ifdef MP-WEIXIN
platform: 'mp-weixin',
apiBaseUrl: 'https://api.example.com/mp',
features: {
share: true,
payment: true,
camera: true,
location: true
}
// #endif
};
export default platformConfig;
3. Error Handling
Implement platform-aware error handling:
// utils/error-handler.js
class ErrorHandler {
static handle(error, context = '') {
console.error(`[${context}] Error:`, error);
// #ifdef APP-PLUS
// App platform can show native alerts
plus.nativeUI.alert(error.message || 'An error occurred', () => {}, 'Error', 'OK');
// #endif
// #ifdef H5
// H5 platform use browser alert
alert(error.message || 'An error occurred');
// #endif
// #ifdef MP
// Mini Program use uni.showModal
uni.showModal({
title: 'Error',
content: error.message || 'An error occurred',
showCancel: false
});
// #endif
// Report error to analytics service
this.reportError(error, context);
}
static reportError(error, context) {
// Platform-specific error reporting
const errorData = {
message: error.message,
stack: error.stack,
context,
platform: this.getPlatform(),
timestamp: new Date().toISOString()
};
// Send to error reporting service
uni.request({
url: 'https://api.example.com/errors',
method: 'POST',
data: errorData,
fail: (err) => {
console.error('Failed to report error:', err);
}
});
}
static getPlatform() {
// #ifdef APP-PLUS
return 'app';
// #endif
// #ifdef H5
return 'h5';
// #endif
// #ifdef MP-WEIXIN
return 'mp-weixin';
// #endif
// #ifdef MP-ALIPAY
return 'mp-alipay';
// #endif
return 'unknown';
}
}
export default ErrorHandler;
4. Performance Monitoring
Monitor performance across different platforms:
// utils/performance.js
class PerformanceMonitor {
constructor() {
this.metrics = {};
this.startTimes = {};
}
/**
* Start timing
* @param {string} name Metric name
*/
start(name) {
this.startTimes[name] = Date.now();
}
/**
* End timing
* @param {string} name Metric name
*/
end(name) {
if (!this.startTimes[name]) {
console.warn(`Performance metric "${name}" was not started`);
return;
}
const duration = Date.now() - this.startTimes[name];
this.metrics[name] = duration;
console.log(`Performance: ${name} took ${duration}ms`);
// Report to analytics
this.report(name, duration);
delete this.startTimes[name];
}
/**
* Report performance metrics
* @param {string} name Metric name
* @param {number} duration Duration in milliseconds
*/
report(name, duration) {
const data = {
metric: name,
duration,
platform: this.getPlatform(),
timestamp: new Date().toISOString()
};
// #ifdef APP-PLUS
// App platform specific reporting
data.appVersion = plus.runtime.version;
data.deviceInfo = plus.device.model;
// #endif
// #ifdef H5
// H5 platform specific reporting
data.userAgent = navigator.userAgent;
data.connection = navigator.connection?.effectiveType || 'unknown';
// #endif
// Send to analytics service
uni.request({
url: 'https://api.example.com/performance',
method: 'POST',
data,
fail: (err) => {
console.error('Failed to report performance:', err);
}
});
}
getPlatform() {
// #ifdef APP-PLUS
return 'app';
// #endif
// #ifdef H5
return 'h5';
// #endif
// #ifdef MP-WEIXIN
return 'mp-weixin';
// #endif
return 'unknown';
}
}
// Create global instance
const performanceMonitor = new PerformanceMonitor();
export default performanceMonitor;
Summary
Platform adaptation is crucial for uni-app development success. Key strategies include:
- Conditional Compilation: Use
#ifdef
and#ifndef
to write platform-specific code - Unified APIs: Create wrapper functions to abstract platform differences
- Style Adaptation: Handle CSS compatibility and responsive design
- Feature Detection: Check API availability before use
- Error Handling: Implement platform-aware error management
- Performance Optimization: Apply platform-specific optimizations
- Testing Strategy: Test thoroughly on all target platforms
By following these practices, you can create robust cross-platform applications that provide consistent user experiences while leveraging platform-specific capabilities when needed.