Skip to content

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

js
// #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

ValuePlatform
APP-PLUSApp
APP-PLUS-NVUEApp nvue
H5H5
MP-WEIXINWeChat Mini Program
MP-ALIPAYAlipay Mini Program
MP-BAIDUBaidu Mini Program
MP-TOUTIAOByteDance Mini Program
MP-QQQQ Mini Program
MP-KUAISHOUKuaishou Mini Program
MPAll Mini Programs
QUICKAPP-WEBVIEWQuickApp Universal
QUICKAPP-WEBVIEW-UNIONQuickApp Union
QUICKAPP-WEBVIEW-HUAWEIQuickApp Huawei

3. Multi-platform Conditional Compilation

js
// #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

html
<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

js
<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

css
<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:

js
// 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:

js
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:

js
// 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.

css
.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:

css
/* 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:

css
/* 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:

css
/* 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:

html
<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:

html
<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:

html
<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:

js
// 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:

js
// 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:

js
// 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:

html
<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:

js
// 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:

js
// 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:

js
// 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:

js
// 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:

js
// 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:

  1. Conditional Compilation: Use #ifdef and #ifndef to write platform-specific code
  2. Unified APIs: Create wrapper functions to abstract platform differences
  3. Style Adaptation: Handle CSS compatibility and responsive design
  4. Feature Detection: Check API availability before use
  5. Error Handling: Implement platform-aware error management
  6. Performance Optimization: Apply platform-specific optimizations
  7. 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.

一次开发,多端部署 - 让跨平台开发更简单