本章将深入探讨 UniApp 的高级开发技巧,包括原生插件开发、混合开发模式、第三方 SDK 集成、自定义编译配置等内容,帮助开发者掌握更复杂的开发场景和技术实现。

9.1 原生插件开发

9.1.1 插件开发基础

插件项目结构

my-native-plugin/
├── package.json
├── plugin.xml
├── android/
│   ├── src/
│   │   └── main/
│   │       ├── java/
│   │       │   └── com/
│   │       │       └── example/
│   │       │           └── plugin/
│   │       │               └── MyPlugin.java
│   │       └── assets/
│   └── build.gradle
├── ios/
│   ├── MyPlugin/
│   │   ├── MyPlugin.h
│   │   ├── MyPlugin.m
│   │   └── MyPluginProxy.h
│   └── MyPlugin.podspec
└── js/
    └── index.js

package.json 配置

{
  "name": "my-native-plugin",
  "version": "1.0.0",
  "description": "UniApp 原生插件示例",
  "main": "js/index.js",
  "uni_modules": {
    "dependencies": [],
    "encrypt": [],
    "platforms": {
      "cloud": {
        "tcb": "y",
        "aliyun": "y"
      },
      "client": {
        "App": {
          "app-vue": "y",
          "app-nvue": "y"
        },
        "H5-mobile": {
          "Safari": "y",
          "Android Browser": "y",
          "微信浏览器(Android)": "y",
          "QQ浏览器(Android)": "y"
        },
        "H5-pc": {
          "Chrome": "y",
          "IE": "y",
          "Edge": "y",
          "Firefox": "y",
          "Safari": "y"
        },
        "小程序": {
          "微信": "y",
          "阿里": "y",
          "百度": "y",
          "字节跳动": "y",
          "QQ": "y"
        },
        "快应用": {
          "华为": "y",
          "联盟": "y"
        }
      }
    }
  },
  "keywords": [
    "uniapp",
    "plugin",
    "native"
  ],
  "author": "Your Name",
  "license": "MIT"
}

plugin.xml 配置

<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0"
        id="my-native-plugin"
        version="1.0.0">
    
    <name>MyNativePlugin</name>
    <description>UniApp 原生插件示例</description>
    <license>MIT</license>
    <keywords>uniapp,plugin,native</keywords>
    
    <engines>
        <engine name="HBuilder" version=">=3.0.0"/>
    </engines>
    
    <!-- JavaScript 接口 -->
    <js-module src="js/index.js" name="MyPlugin">
        <clobbers target="MyPlugin" />
    </js-module>
    
    <!-- Android 配置 -->
    <platform name="android">
        <config-file target="res/xml/config.xml" parent="/*">
            <feature name="MyPlugin">
                <param name="android-package" value="com.example.plugin.MyPlugin"/>
            </feature>
        </config-file>
        
        <source-file src="android/src/main/java/com/example/plugin/MyPlugin.java" 
                     target-dir="src/com/example/plugin"/>
        
        <config-file target="AndroidManifest.xml" parent="/manifest">
            <uses-permission android:name="android.permission.CAMERA" />
            <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        </config-file>
    </platform>
    
    <!-- iOS 配置 -->
    <platform name="ios">
        <config-file target="config.xml" parent="/*">
            <feature name="MyPlugin">
                <param name="ios-package" value="MyPlugin" />
            </feature>
        </config-file>
        
        <header-file src="ios/MyPlugin/MyPlugin.h" />
        <source-file src="ios/MyPlugin/MyPlugin.m" />
        
        <config-file target="*-Info.plist" parent="/*">
            <key>NSCameraUsageDescription</key>
            <string>需要访问相机权限</string>
            <key>NSPhotoLibraryUsageDescription</key>
            <string>需要访问相册权限</string>
        </config-file>
    </platform>
    
</plugin>

9.1.2 Android 插件开发

Android 插件主类

// android/src/main/java/com/example/plugin/MyPlugin.java
package com.example.plugin;

import android.content.Context;
import android.content.Intent;
import android.hardware.Camera;
import android.os.Build;
import android.util.Log;

import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.PluginResult;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.HashMap;
import java.util.Map;

public class MyPlugin extends CordovaPlugin {
    
    private static final String TAG = "MyPlugin";
    
    // 插件方法名称常量
    private static final String ACTION_GET_DEVICE_INFO = "getDeviceInfo";
    private static final String ACTION_OPEN_CAMERA = "openCamera";
    private static final String ACTION_VIBRATE = "vibrate";
    private static final String ACTION_SHOW_TOAST = "showToast";
    
    @Override
    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
        Log.d(TAG, "执行方法: " + action);
        
        try {
            switch (action) {
                case ACTION_GET_DEVICE_INFO:
                    getDeviceInfo(callbackContext);
                    return true;
                    
                case ACTION_OPEN_CAMERA:
                    openCamera(callbackContext);
                    return true;
                    
                case ACTION_VIBRATE:
                    int duration = args.getInt(0);
                    vibrate(duration, callbackContext);
                    return true;
                    
                case ACTION_SHOW_TOAST:
                    String message = args.getString(0);
                    showToast(message, callbackContext);
                    return true;
                    
                default:
                    callbackContext.error("未知的方法: " + action);
                    return false;
            }
        } catch (Exception e) {
            Log.e(TAG, "执行方法失败: " + action, e);
            callbackContext.error("执行失败: " + e.getMessage());
            return false;
        }
    }
    
    /**
     * 获取设备信息
     */
    private void getDeviceInfo(CallbackContext callbackContext) {
        try {
            Context context = cordova.getActivity().getApplicationContext();
            
            JSONObject deviceInfo = new JSONObject();
            deviceInfo.put("model", Build.MODEL);
            deviceInfo.put("manufacturer", Build.MANUFACTURER);
            deviceInfo.put("version", Build.VERSION.RELEASE);
            deviceInfo.put("sdk", Build.VERSION.SDK_INT);
            deviceInfo.put("brand", Build.BRAND);
            deviceInfo.put("device", Build.DEVICE);
            deviceInfo.put("board", Build.BOARD);
            deviceInfo.put("host", Build.HOST);
            deviceInfo.put("fingerprint", Build.FINGERPRINT);
            
            // 获取应用信息
            JSONObject appInfo = new JSONObject();
            appInfo.put("packageName", context.getPackageName());
            appInfo.put("versionName", context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName);
            appInfo.put("versionCode", context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode);
            
            deviceInfo.put("app", appInfo);
            
            callbackContext.success(deviceInfo);
            
        } catch (Exception e) {
            Log.e(TAG, "获取设备信息失败", e);
            callbackContext.error("获取设备信息失败: " + e.getMessage());
        }
    }
    
    /**
     * 打开相机
     */
    private void openCamera(CallbackContext callbackContext) {
        try {
            // 检查相机权限
            if (!hasPermission("android.permission.CAMERA")) {
                callbackContext.error("没有相机权限");
                return;
            }
            
            // 检查相机是否可用
            if (!isCameraAvailable()) {
                callbackContext.error("相机不可用");
                return;
            }
            
            // 启动相机 Intent
            Intent cameraIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
            if (cameraIntent.resolveActivity(cordova.getActivity().getPackageManager()) != null) {
                cordova.startActivityForResult(this, cameraIntent, 100);
                
                // 保存回调上下文
                this.cameraCallbackContext = callbackContext;
            } else {
                callbackContext.error("没有可用的相机应用");
            }
            
        } catch (Exception e) {
            Log.e(TAG, "打开相机失败", e);
            callbackContext.error("打开相机失败: " + e.getMessage());
        }
    }
    
    /**
     * 震动
     */
    private void vibrate(int duration, CallbackContext callbackContext) {
        try {
            android.os.Vibrator vibrator = (android.os.Vibrator) cordova.getActivity().getSystemService(Context.VIBRATOR_SERVICE);
            
            if (vibrator != null && vibrator.hasVibrator()) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    vibrator.vibrate(android.os.VibrationEffect.createOneShot(duration, android.os.VibrationEffect.DEFAULT_AMPLITUDE));
                } else {
                    vibrator.vibrate(duration);
                }
                
                callbackContext.success("震动成功");
            } else {
                callbackContext.error("设备不支持震动");
            }
            
        } catch (Exception e) {
            Log.e(TAG, "震动失败", e);
            callbackContext.error("震动失败: " + e.getMessage());
        }
    }
    
    /**
     * 显示 Toast
     */
    private void showToast(String message, CallbackContext callbackContext) {
        try {
            cordova.getActivity().runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    android.widget.Toast.makeText(cordova.getActivity(), message, android.widget.Toast.LENGTH_SHORT).show();
                }
            });
            
            callbackContext.success("Toast 显示成功");
            
        } catch (Exception e) {
            Log.e(TAG, "显示 Toast 失败", e);
            callbackContext.error("显示 Toast 失败: " + e.getMessage());
        }
    }
    
    /**
     * 检查权限
     */
    private boolean hasPermission(String permission) {
        return cordova.hasPermission(permission);
    }
    
    /**
     * 检查相机是否可用
     */
    private boolean isCameraAvailable() {
        try {
            Camera camera = Camera.open();
            if (camera != null) {
                camera.release();
                return true;
            }
        } catch (Exception e) {
            Log.e(TAG, "检查相机可用性失败", e);
        }
        return false;
    }
    
    // 相机回调上下文
    private CallbackContext cameraCallbackContext;
    
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
        if (requestCode == 100 && cameraCallbackContext != null) {
            if (resultCode == cordova.getActivity().RESULT_OK) {
                cameraCallbackContext.success("相机拍照成功");
            } else {
                cameraCallbackContext.error("相机拍照取消或失败");
            }
            cameraCallbackContext = null;
        }
    }
}

Android Gradle 配置

// android/build.gradle
apply plugin: 'com.android.library'

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"
    
    defaultConfig {
        minSdkVersion 19
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"
    }
    
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation 'androidx.appcompat:appcompat:1.3.1'
    implementation 'com.google.android.material:material:1.4.0'
    
    // Cordova 依赖
    compileOnly 'org.apache.cordova:framework:10.1.1'
}

9.1.3 iOS 插件开发

iOS 插件头文件

// ios/MyPlugin/MyPlugin.h
#import <Cordova/CDVPlugin.h>
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>

@interface MyPlugin : CDVPlugin <UIImagePickerControllerDelegate, UINavigationControllerDelegate>

@property (nonatomic, strong) NSString* cameraCallbackId;

// 插件方法声明
- (void)getDeviceInfo:(CDVInvokedUrlCommand*)command;
- (void)openCamera:(CDVInvokedUrlCommand*)command;
- (void)vibrate:(CDVInvokedUrlCommand*)command;
- (void)showToast:(CDVInvokedUrlCommand*)command;

@end

iOS 插件实现文件

// ios/MyPlugin/MyPlugin.m
#import "MyPlugin.h"
#import <AudioToolbox/AudioToolbox.h>
#import <sys/utsname.h>

@implementation MyPlugin

#pragma mark - 获取设备信息

- (void)getDeviceInfo:(CDVInvokedUrlCommand*)command {
    @try {
        NSMutableDictionary* deviceInfo = [[NSMutableDictionary alloc] init];
        
        // 设备基本信息
        UIDevice* device = [UIDevice currentDevice];
        [deviceInfo setObject:[device model] forKey:@"model"];
        [deviceInfo setObject:[device systemName] forKey:@"platform"];
        [deviceInfo setObject:[device systemVersion] forKey:@"version"];
        [deviceInfo setObject:[device name] forKey:@"name"];
        
        // 获取设备型号
        struct utsname systemInfo;
        uname(&systemInfo);
        NSString* deviceModel = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
        [deviceInfo setObject:deviceModel forKey:@"deviceModel"];
        
        // 屏幕信息
        CGRect screenBounds = [[UIScreen mainScreen] bounds];
        CGFloat screenScale = [[UIScreen mainScreen] scale];
        
        NSMutableDictionary* screenInfo = [[NSMutableDictionary alloc] init];
        [screenInfo setObject:@(screenBounds.size.width) forKey:@"width"];
        [screenInfo setObject:@(screenBounds.size.height) forKey:@"height"];
        [screenInfo setObject:@(screenScale) forKey:@"scale"];
        [deviceInfo setObject:screenInfo forKey:@"screen"];
        
        // 应用信息
        NSBundle* mainBundle = [NSBundle mainBundle];
        NSMutableDictionary* appInfo = [[NSMutableDictionary alloc] init];
        [appInfo setObject:[mainBundle bundleIdentifier] forKey:@"bundleId"];
        [appInfo setObject:[mainBundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"] forKey:@"version"];
        [appInfo setObject:[mainBundle objectForInfoDictionaryKey:@"CFBundleVersion"] forKey:@"build"];
        [deviceInfo setObject:appInfo forKey:@"app"];
        
        CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:deviceInfo];
        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
        
    } @catch (NSException* exception) {
        NSLog(@"获取设备信息失败: %@", exception.reason);
        CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:exception.reason];
        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
    }
}

#pragma mark - 打开相机

- (void)openCamera:(CDVInvokedUrlCommand*)command {
    @try {
        // 检查相机权限
        AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
        
        if (authStatus == AVAuthorizationStatusDenied || authStatus == AVAuthorizationStatusRestricted) {
            CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"没有相机权限"];
            [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
            return;
        }
        
        // 检查相机是否可用
        if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
            CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"相机不可用"];
            [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
            return;
        }
        
        // 保存回调ID
        self.cameraCallbackId = command.callbackId;
        
        // 创建图像选择器
        UIImagePickerController* imagePicker = [[UIImagePickerController alloc] init];
        imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
        imagePicker.delegate = self;
        imagePicker.allowsEditing = NO;
        
        // 在主线程中显示相机
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.viewController presentViewController:imagePicker animated:YES completion:nil];
        });
        
    } @catch (NSException* exception) {
        NSLog(@"打开相机失败: %@", exception.reason);
        CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:exception.reason];
        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
    }
}

#pragma mark - 震动

- (void)vibrate:(CDVInvokedUrlCommand*)command {
    @try {
        NSNumber* duration = [command.arguments objectAtIndex:0];
        
        // iOS 系统震动
        AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
        
        CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:@"震动成功"];
        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
        
    } @catch (NSException* exception) {
        NSLog(@"震动失败: %@", exception.reason);
        CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:exception.reason];
        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
    }
}

#pragma mark - 显示 Toast

- (void)showToast:(CDVInvokedUrlCommand*)command {
    @try {
        NSString* message = [command.arguments objectAtIndex:0];
        
        dispatch_async(dispatch_get_main_queue(), ^{
            // 创建 Alert
            UIAlertController* alert = [UIAlertController alertControllerWithTitle:nil
                                                                           message:message
                                                                    preferredStyle:UIAlertControllerStyleAlert];
            
            // 显示 Alert
            [self.viewController presentViewController:alert animated:YES completion:nil];
            
            // 2秒后自动关闭
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [alert dismissViewControllerAnimated:YES completion:nil];
            });
        });
        
        CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:@"Toast 显示成功"];
        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
        
    } @catch (NSException* exception) {
        NSLog(@"显示 Toast 失败: %@", exception.reason);
        CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:exception.reason];
        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
    }
}

#pragma mark - UIImagePickerControllerDelegate

- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id>*)info {
    [picker dismissViewControllerAnimated:YES completion:nil];
    
    if (self.cameraCallbackId) {
        CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:@"相机拍照成功"];
        [self.commandDelegate sendPluginResult:pluginResult callbackId:self.cameraCallbackId];
        self.cameraCallbackId = nil;
    }
}

- (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker {
    [picker dismissViewControllerAnimated:YES completion:nil];
    
    if (self.cameraCallbackId) {
        CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"相机拍照取消"];
        [self.commandDelegate sendPluginResult:pluginResult callbackId:self.cameraCallbackId];
        self.cameraCallbackId = nil;
    }
}

@end

9.1.4 JavaScript 接口

// js/index.js
class MyNativePlugin {
  constructor() {
    this.isReady = false;
    this.readyCallbacks = [];
    
    // 等待设备就绪
    document.addEventListener('deviceready', () => {
      this.isReady = true;
      this.readyCallbacks.forEach(callback => callback());
      this.readyCallbacks = [];
    }, false);
  }
  
  // 确保设备就绪
  ready(callback) {
    if (this.isReady) {
      callback();
    } else {
      this.readyCallbacks.push(callback);
    }
  }
  
  // 执行原生方法
  exec(action, args = []) {
    return new Promise((resolve, reject) => {
      this.ready(() => {
        if (typeof cordova !== 'undefined' && cordova.exec) {
          cordova.exec(
            (result) => resolve(result),
            (error) => reject(new Error(error)),
            'MyPlugin',
            action,
            args
          );
        } else {
          reject(new Error('Cordova 环境不可用'));
        }
      });
    });
  }
  
  // 获取设备信息
  async getDeviceInfo() {
    try {
      const deviceInfo = await this.exec('getDeviceInfo');
      return {
        success: true,
        data: deviceInfo
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
  
  // 打开相机
  async openCamera() {
    try {
      const result = await this.exec('openCamera');
      return {
        success: true,
        message: result
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
  
  // 震动
  async vibrate(duration = 500) {
    try {
      const result = await this.exec('vibrate', [duration]);
      return {
        success: true,
        message: result
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
  
  // 显示 Toast
  async showToast(message) {
    try {
      const result = await this.exec('showToast', [message]);
      return {
        success: true,
        message: result
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
  
  // 检查平台支持
  isSupported() {
    return typeof cordova !== 'undefined' && 
           (cordova.platformId === 'android' || cordova.platformId === 'ios');
  }
  
  // 获取平台信息
  getPlatform() {
    if (typeof cordova !== 'undefined') {
      return cordova.platformId;
    }
    return 'unknown';
  }
}

// 创建插件实例
const myPlugin = new MyNativePlugin();

// 导出插件
if (typeof module !== 'undefined' && module.exports) {
  module.exports = myPlugin;
}

if (typeof window !== 'undefined') {
  window.MyPlugin = myPlugin;
}

export default myPlugin;

9.2 混合开发模式

9.2.1 原生容器集成

Android 原生容器

// HybridActivity.java
package com.example.hybrid;

import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.webkit.WebSettings;
import androidx.appcompat.app.AppCompatActivity;

public class HybridActivity extends AppCompatActivity {
    
    private WebView webView;
    private HybridBridge hybridBridge;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_hybrid);
        
        initWebView();
        initBridge();
        loadUniApp();
    }
    
    private void initWebView() {
        webView = findViewById(R.id.webview);
        
        WebSettings settings = webView.getSettings();
        settings.setJavaScriptEnabled(true);
        settings.setDomStorageEnabled(true);
        settings.setAllowFileAccess(true);
        settings.setAllowContentAccess(true);
        settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
        
        webView.setWebViewClient(new WebViewClient() {
            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
                // 页面加载完成后注入桥接对象
                hybridBridge.injectBridge();
            }
        });
    }
    
    private void initBridge() {
        hybridBridge = new HybridBridge(this, webView);
        webView.addJavascriptInterface(hybridBridge, "AndroidBridge");
    }
    
    private void loadUniApp() {
        // 加载 UniApp 页面
        String url = "file:///android_asset/www/index.html";
        webView.loadUrl(url);
    }
    
    @Override
    public void onBackPressed() {
        if (webView.canGoBack()) {
            webView.goBack();
        } else {
            super.onBackPressed();
        }
    }
    
    @Override
    protected void onDestroy() {
        if (webView != null) {
            webView.destroy();
        }
        super.onDestroy();
    }
}

Android 桥接类

// HybridBridge.java
package com.example.hybrid;

import android.app.Activity;
import android.content.Context;
import android.webkit.JavascriptInterface;
import android.webkit.WebView;
import android.widget.Toast;

import org.json.JSONObject;

public class HybridBridge {
    
    private Activity activity;
    private WebView webView;
    
    public HybridBridge(Activity activity, WebView webView) {
        this.activity = activity;
        this.webView = webView;
    }
    
    @JavascriptInterface
    public void showToast(String message) {
        activity.runOnUiThread(() -> {
            Toast.makeText(activity, message, Toast.LENGTH_SHORT).show();
        });
    }
    
    @JavascriptInterface
    public String getDeviceInfo() {
        try {
            JSONObject deviceInfo = new JSONObject();
            deviceInfo.put("platform", "android");
            deviceInfo.put("model", android.os.Build.MODEL);
            deviceInfo.put("version", android.os.Build.VERSION.RELEASE);
            
            return deviceInfo.toString();
        } catch (Exception e) {
            return "{}";
        }
    }
    
    @JavascriptInterface
    public void callNativeMethod(String methodName, String params, String callbackId) {
        activity.runOnUiThread(() -> {
            try {
                // 处理原生方法调用
                String result = handleNativeCall(methodName, params);
                
                // 回调 JavaScript
                String jsCode = String.format("window.HybridCallback.success('%s', '%s')", callbackId, result);
                webView.evaluateJavascript(jsCode, null);
                
            } catch (Exception e) {
                String jsCode = String.format("window.HybridCallback.error('%s', '%s')", callbackId, e.getMessage());
                webView.evaluateJavascript(jsCode, null);
            }
        });
    }
    
    private String handleNativeCall(String methodName, String params) {
        switch (methodName) {
            case "vibrate":
                return handleVibrate(params);
            case "openCamera":
                return handleOpenCamera(params);
            default:
                throw new RuntimeException("未知的方法: " + methodName);
        }
    }
    
    private String handleVibrate(String params) {
        // 实现震动功能
        return "震动成功";
    }
    
    private String handleOpenCamera(String params) {
        // 实现打开相机功能
        return "相机打开成功";
    }
    
    public void injectBridge() {
        String jsCode = ""
            + "window.HybridCallback = {"
            + "  success: function(callbackId, result) {"
            + "    if (window.hybridCallbacks && window.hybridCallbacks[callbackId]) {"
            + "      window.hybridCallbacks[callbackId].success(result);"
            + "      delete window.hybridCallbacks[callbackId];"
            + "    }"
            + "  },"
            + "  error: function(callbackId, error) {"
            + "    if (window.hybridCallbacks && window.hybridCallbacks[callbackId]) {"
            + "      window.hybridCallbacks[callbackId].error(error);"
            + "      delete window.hybridCallbacks[callbackId];"
            + "    }"
            + "  }"
            + "};"
            + "window.hybridCallbacks = {};"
            + "window.HybridBridge = {"
            + "  call: function(methodName, params) {"
            + "    return new Promise(function(resolve, reject) {"
            + "      var callbackId = 'cb_' + Date.now() + '_' + Math.random();"
            + "      window.hybridCallbacks[callbackId] = {"
            + "        success: resolve,"
            + "        error: reject"
            + "      };"
            + "      AndroidBridge.callNativeMethod(methodName, JSON.stringify(params), callbackId);"
            + "    });"
            + "  }"
            + "};"
            + "console.log('Hybrid Bridge 注入成功');"
            ;
        
        webView.evaluateJavascript(jsCode, null);
    }
}

9.2.2 iOS 原生容器

iOS 容器视图控制器

// HybridViewController.h
#import <UIKit/UIKit.h>
#import <WebKit/WebKit.h>

@interface HybridViewController : UIViewController <WKNavigationDelegate, WKScriptMessageHandler>

@property (nonatomic, strong) WKWebView *webView;
@property (nonatomic, strong) WKUserContentController *userContentController;

@end

// HybridViewController.m
#import "HybridViewController.h"

@implementation HybridViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self setupWebView];
    [self setupBridge];
    [self loadUniApp];
}

- (void)setupWebView {
    WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
    
    // 配置用户内容控制器
    self.userContentController = [[WKUserContentController alloc] init];
    config.userContentController = self.userContentController;
    
    // 允许内联播放
    config.allowsInlineMediaPlayback = YES;
    
    // 创建 WebView
    self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
    self.webView.navigationDelegate = self;
    self.webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    
    [self.view addSubview:self.webView];
}

- (void)setupBridge {
    // 注册消息处理器
    [self.userContentController addScriptMessageHandler:self name:@"showToast"];
    [self.userContentController addScriptMessageHandler:self name:@"getDeviceInfo"];
    [self.userContentController addScriptMessageHandler:self name:@"callNativeMethod"];
}

- (void)loadUniApp {
    NSString *htmlPath = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html" inDirectory:@"www"];
    NSURL *htmlURL = [NSURL fileURLWithPath:htmlPath];
    NSURL *baseURL = [htmlURL URLByDeletingLastPathComponent];
    
    NSString *htmlContent = [NSString stringWithContentsOfURL:htmlURL encoding:NSUTF8StringEncoding error:nil];
    [self.webView loadHTMLString:htmlContent baseURL:baseURL];
}

#pragma mark - WKNavigationDelegate

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
    // 页面加载完成后注入桥接对象
    [self injectBridge];
}

#pragma mark - WKScriptMessageHandler

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    NSString *name = message.name;
    id body = message.body;
    
    if ([name isEqualToString:@"showToast"]) {
        [self handleShowToast:body];
    } else if ([name isEqualToString:@"getDeviceInfo"]) {
        [self handleGetDeviceInfo:body];
    } else if ([name isEqualToString:@"callNativeMethod"]) {
        [self handleCallNativeMethod:body];
    }
}

- (void)handleShowToast:(id)params {
    NSString *message = params[@"message"];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil
                                                                       message:message
                                                                preferredStyle:UIAlertControllerStyleAlert];
        
        [self presentViewController:alert animated:YES completion:nil];
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [alert dismissViewControllerAnimated:YES completion:nil];
        });
    });
}

- (void)handleGetDeviceInfo:(id)params {
    UIDevice *device = [UIDevice currentDevice];
    
    NSDictionary *deviceInfo = @{
        @"platform": @"ios",
        @"model": [device model],
        @"version": [device systemVersion],
        @"name": [device name]
    };
    
    NSString *callbackId = params[@"callbackId"];
    [self callJavaScriptCallback:callbackId withResult:deviceInfo error:nil];
}

- (void)handleCallNativeMethod:(id)params {
    NSString *methodName = params[@"methodName"];
    NSDictionary *methodParams = params[@"params"];
    NSString *callbackId = params[@"callbackId"];
    
    @try {
        id result = [self handleNativeCall:methodName withParams:methodParams];
        [self callJavaScriptCallback:callbackId withResult:result error:nil];
    } @catch (NSException *exception) {
        [self callJavaScriptCallback:callbackId withResult:nil error:exception.reason];
    }
}

- (id)handleNativeCall:(NSString *)methodName withParams:(NSDictionary *)params {
    if ([methodName isEqualToString:@"vibrate"]) {
        return [self handleVibrate:params];
    } else if ([methodName isEqualToString:@"openCamera"]) {
        return [self handleOpenCamera:params];
    } else {
        @throw [NSException exceptionWithName:@"UnknownMethod" reason:[NSString stringWithFormat:@"未知的方法: %@", methodName] userInfo:nil];
    }
}

- (NSString *)handleVibrate:(NSDictionary *)params {
    AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
    return @"震动成功";
}

- (NSString *)handleOpenCamera:(NSDictionary *)params {
    // 实现打开相机功能
    return @"相机打开成功";
}

- (void)callJavaScriptCallback:(NSString *)callbackId withResult:(id)result error:(NSString *)error {
    NSString *jsCode;
    
    if (error) {
        jsCode = [NSString stringWithFormat:@"window.HybridCallback.error('%@', '%@')", callbackId, error];
    } else {
        NSData *jsonData = [NSJSONSerialization dataWithJSONObject:result options:0 error:nil];
        NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
        jsCode = [NSString stringWithFormat:@"window.HybridCallback.success('%@', %@)", callbackId, jsonString];
    }
    
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.webView evaluateJavaScript:jsCode completionHandler:nil];
    });
}

- (void)injectBridge {
    NSString *jsCode = @""
        "window.HybridCallback = {"
        "  success: function(callbackId, result) {"
        "    if (window.hybridCallbacks && window.hybridCallbacks[callbackId]) {"
        "      window.hybridCallbacks[callbackId].success(result);"
        "      delete window.hybridCallbacks[callbackId];"
        "    }"
        "  },"
        "  error: function(callbackId, error) {"
        "    if (window.hybridCallbacks && window.hybridCallbacks[callbackId]) {"
        "      window.hybridCallbacks[callbackId].error(error);"
        "      delete window.hybridCallbacks[callbackId];"
        "    }"
        "  }"
        "};"
        "window.hybridCallbacks = {};"
        "window.HybridBridge = {"
        "  call: function(methodName, params) {"
        "    return new Promise(function(resolve, reject) {"
        "      var callbackId = 'cb_' + Date.now() + '_' + Math.random();"
        "      window.hybridCallbacks[callbackId] = {"
        "        success: resolve,"
        "        error: reject"
        "      };"
        "      window.webkit.messageHandlers.callNativeMethod.postMessage({"
        "        methodName: methodName,"
        "        params: params,"
        "        callbackId: callbackId"
        "      });"
        "    });"
        "  }"
        "};"
        "console.log('Hybrid Bridge 注入成功');"
        ;
    
    [self.webView evaluateJavaScript:jsCode completionHandler:nil];
}

- (void)dealloc {
    [self.userContentController removeScriptMessageHandlerForName:@"showToast"];
    [self.userContentController removeScriptMessageHandlerForName:@"getDeviceInfo"];
    [self.userContentController removeScriptMessageHandlerForName:@"callNativeMethod"];
}

@end

9.2.3 JavaScript 桥接层

// utils/hybridBridge.js
class HybridBridge {
  constructor() {
    this.isReady = false;
    this.readyCallbacks = [];
    this.platform = this.detectPlatform();
    
    this.init();
  }
  
  // 检测平台
  detectPlatform() {
    const userAgent = navigator.userAgent;
    
    if (/Android/i.test(userAgent)) {
      return 'android';
    } else if (/iPhone|iPad|iPod/i.test(userAgent)) {
      return 'ios';
    } else {
      return 'web';
    }
  }
  
  // 初始化桥接
  init() {
    if (this.platform === 'web') {
      // Web 环境,直接标记为就绪
      this.markReady();
      return;
    }
    
    // 等待原生桥接注入
    const checkBridge = () => {
      if (this.isBridgeAvailable()) {
        this.markReady();
      } else {
        setTimeout(checkBridge, 100);
      }
    };
    
    checkBridge();
  }
  
  // 检查桥接是否可用
  isBridgeAvailable() {
    if (this.platform === 'android') {
      return typeof window.AndroidBridge !== 'undefined' && typeof window.HybridBridge !== 'undefined';
    } else if (this.platform === 'ios') {
      return typeof window.webkit !== 'undefined' && 
             typeof window.webkit.messageHandlers !== 'undefined' &&
             typeof window.HybridBridge !== 'undefined';
    }
    return false;
  }
  
  // 标记为就绪
  markReady() {
    this.isReady = true;
    this.readyCallbacks.forEach(callback => callback());
    this.readyCallbacks = [];
  }
  
  // 等待就绪
  ready(callback) {
    if (this.isReady) {
      callback();
    } else {
      this.readyCallbacks.push(callback);
    }
  }
  
  // 调用原生方法
  async callNative(methodName, params = {}) {
    return new Promise((resolve, reject) => {
      this.ready(async () => {
        try {
          if (this.platform === 'web') {
            // Web 环境模拟
            resolve(this.simulateNativeCall(methodName, params));
            return;
          }
          
          const result = await window.HybridBridge.call(methodName, params);
          resolve(result);
        } catch (error) {
          reject(error);
        }
      });
    });
  }
  
  // Web 环境模拟原生调用
  simulateNativeCall(methodName, params) {
    switch (methodName) {
      case 'getDeviceInfo':
        return {
          platform: 'web',
          userAgent: navigator.userAgent,
          language: navigator.language,
          cookieEnabled: navigator.cookieEnabled
        };
        
      case 'showToast':
        console.log('Toast:', params.message);
        return 'Toast 显示成功(Web 模拟)';
        
      case 'vibrate':
        if (navigator.vibrate) {
          navigator.vibrate(params.duration || 500);
          return '震动成功';
        } else {
          return '设备不支持震动';
        }
        
      default:
        throw new Error(`未知的方法: ${methodName}`);
    }
  }
  
  // 显示 Toast
  async showToast(message) {
    if (this.platform === 'android' && typeof window.AndroidBridge !== 'undefined') {
      window.AndroidBridge.showToast(message);
      return 'Toast 显示成功';
    } else if (this.platform === 'ios' && typeof window.webkit !== 'undefined') {
      window.webkit.messageHandlers.showToast.postMessage({ message });
      return 'Toast 显示成功';
    } else {
      return this.callNative('showToast', { message });
    }
  }
  
  // 获取设备信息
  async getDeviceInfo() {
    if (this.platform === 'android' && typeof window.AndroidBridge !== 'undefined') {
      const info = window.AndroidBridge.getDeviceInfo();
      return JSON.parse(info);
    } else if (this.platform === 'ios' && typeof window.webkit !== 'undefined') {
      return new Promise((resolve) => {
        const callbackId = 'deviceInfo_' + Date.now();
        window.hybridCallbacks = window.hybridCallbacks || {};
        window.hybridCallbacks[callbackId] = {
          success: resolve,
          error: resolve
        };
        
        window.webkit.messageHandlers.getDeviceInfo.postMessage({ callbackId });
      });
    } else {
      return this.callNative('getDeviceInfo');
    }
  }
  
  // 震动
  async vibrate(duration = 500) {
    return this.callNative('vibrate', { duration });
  }
  
  // 打开相机
  async openCamera() {
    return this.callNative('openCamera');
  }
  
  // 获取平台信息
  getPlatform() {
    return this.platform;
  }
  
  // 检查是否为原生环境
  isNative() {
    return this.platform === 'android' || this.platform === 'ios';
  }
  
  // 检查是否为 Web 环境
  isWeb() {
    return this.platform === 'web';
  }
}

// 创建全局实例
const hybridBridge = new HybridBridge();

// 导出
export default hybridBridge;

// 全局挂载
if (typeof window !== 'undefined') {
  window.HybridBridge = hybridBridge;
}

9.3 第三方 SDK 集成

9.3.1 支付 SDK 集成

微信支付集成

// utils/wechatPay.js
class WechatPay {
  constructor(config) {
    this.config = {
      appId: config.appId,
      merchantId: config.merchantId,
      apiKey: config.apiKey,
      notifyUrl: config.notifyUrl,
      ...config
    };
  }
  
  // 发起支付
  async pay(orderInfo) {
    try {
      // 1. 创建预支付订单
      const prepayResult = await this.createPrepayOrder(orderInfo);
      
      if (!prepayResult.success) {
        throw new Error(prepayResult.message);
      }
      
      // 2. 调用支付接口
      const payResult = await this.invokePay(prepayResult.data);
      
      return {
        success: true,
        data: payResult
      };
      
    } catch (error) {
      console.error('微信支付失败:', error);
      return {
        success: false,
        error: error.message
      };
    }
  }
  
  // 创建预支付订单
  async createPrepayOrder(orderInfo) {
    const params = {
      appid: this.config.appId,
      mch_id: this.config.merchantId,
      nonce_str: this.generateNonceStr(),
      body: orderInfo.description,
      out_trade_no: orderInfo.orderNo,
      total_fee: Math.round(orderInfo.amount * 100), // 转换为分
      spbill_create_ip: '127.0.0.1',
      notify_url: this.config.notifyUrl,
      trade_type: this.getTradeType()
    };
    
    // 生成签名
    params.sign = this.generateSign(params);
    
    try {
      const response = await uni.request({
        url: 'https://api.mch.weixin.qq.com/pay/unifiedorder',
        method: 'POST',
        header: {
          'Content-Type': 'application/xml'
        },
        data: this.objectToXml(params)
      });
      
      const result = this.xmlToObject(response.data);
      
      if (result.return_code === 'SUCCESS' && result.result_code === 'SUCCESS') {
        return {
          success: true,
          data: {
            prepayId: result.prepay_id,
            codeUrl: result.code_url
          }
        };
      } else {
        return {
          success: false,
          message: result.return_msg || result.err_code_des
        };
      }
      
    } catch (error) {
      return {
        success: false,
        message: '网络请求失败'
      };
    }
  }
  
  // 调用支付
  async invokePay(prepayData) {
    const platform = uni.getSystemInfoSync().platform;
    
    if (platform === 'android' || platform === 'ios') {
      // App 支付
      return this.invokeAppPay(prepayData);
    } else {
      // H5 支付
      return this.invokeH5Pay(prepayData);
    }
  }
  
  // App 支付
  async invokeAppPay(prepayData) {
    const payParams = {
      appid: this.config.appId,
      partnerid: this.config.merchantId,
      prepayid: prepayData.prepayId,
      package: 'Sign=WXPay',
      noncestr: this.generateNonceStr(),
      timestamp: Math.floor(Date.now() / 1000).toString()
    };
    
    // 生成签名
    payParams.sign = this.generateSign(payParams);
    
    return new Promise((resolve, reject) => {
      uni.requestPayment({
        provider: 'wxpay',
        orderInfo: payParams,
        success: (res) => {
          resolve({
            success: true,
            message: '支付成功',
            data: res
          });
        },
        fail: (err) => {
          reject(new Error(err.errMsg || '支付失败'));
        }
      });
    });
  }
  
  // H5 支付
  async invokeH5Pay(prepayData) {
    // H5 支付需要跳转到微信支付页面
    const payUrl = `https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=${prepayData.prepayId}&package=ETC`;
    
    // 跳转到支付页面
    window.location.href = payUrl;
    
    return {
       success: true,
       message: '正在跳转到支付页面'
     };
   }
   
   // 生成签名
   generateSign(params) {
     // 1. 参数排序
     const sortedKeys = Object.keys(params).sort();
     
     // 2. 拼接参数
     const stringA = sortedKeys
       .filter(key => params[key] !== '' && key !== 'sign')
       .map(key => `${key}=${params[key]}`)
       .join('&');
     
     // 3. RSA2 签名
     return this.rsaSign(stringA, this.config.privateKey);
   }
   
   // RSA 签名
   rsaSign(data, privateKey) {
     // 这里需要引入 RSA 库或使用 crypto-js
     // 示例使用 node-rsa
     const NodeRSA = require('node-rsa');
     const key = new NodeRSA(privateKey);
     return key.sign(data, 'base64');
   }
   
   // 构建订单字符串
   buildOrderString(params) {
     return Object.keys(params)
       .sort()
       .map(key => `${key}=${encodeURIComponent(params[key])}`)
       .join('&');
   }
   
   // 格式化日期
   formatDate(date) {
     const year = date.getFullYear();
     const month = String(date.getMonth() + 1).padStart(2, '0');
     const day = String(date.getDate()).padStart(2, '0');
     const hours = String(date.getHours()).padStart(2, '0');
     const minutes = String(date.getMinutes()).padStart(2, '0');
     const seconds = String(date.getSeconds()).padStart(2, '0');
     
     return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
   }
   
   // 验证支付结果
   async verifyPayResult(paymentData) {
     try {
       const bizContent = {
         out_trade_no: paymentData.out_trade_no,
         trade_no: paymentData.trade_no
       };
       
       const params = {
         app_id: this.config.appId,
         method: 'alipay.trade.query',
         charset: this.config.charset,
         sign_type: this.config.signType,
         timestamp: this.formatDate(new Date()),
         version: this.config.version,
         biz_content: JSON.stringify(bizContent)
       };
       
       params.sign = this.generateSign(params);
       
       const response = await uni.request({
         url: 'https://openapi.alipay.com/gateway.do',
         method: 'POST',
         data: params
       });
       
       return {
         success: response.data.alipay_trade_query_response.code === '10000',
         data: response.data.alipay_trade_query_response
       };
       
     } catch (error) {
       return {
         success: false,
         error: error.message
       };
     }
   }
 }
 
 // 导出支付宝支付类
 export default Alipay;

9.3.2 地图 SDK 集成

高德地图集成

// utils/amapSDK.js
class AmapSDK {
  constructor(config) {
    this.config = {
      key: config.key,
      version: config.version || '2.0',
      plugins: config.plugins || [],
      ...config
    };
    
    this.map = null;
    this.isLoaded = false;
    this.loadPromise = null;
  }
  
  // 加载地图 SDK
  async loadSDK() {
    if (this.isLoaded) {
      return Promise.resolve();
    }
    
    if (this.loadPromise) {
      return this.loadPromise;
    }
    
    this.loadPromise = new Promise((resolve, reject) => {
      // 检查是否在 Web 环境
      if (typeof window === 'undefined') {
        reject(new Error('地图 SDK 只能在 Web 环境中使用'));
        return;
      }
      
      // 检查是否已经加载
      if (window.AMap) {
        this.isLoaded = true;
        resolve();
        return;
      }
      
      // 动态加载脚本
      const script = document.createElement('script');
      script.type = 'text/javascript';
      script.async = true;
      script.src = `https://webapi.amap.com/maps?v=${this.config.version}&key=${this.config.key}&plugin=${this.config.plugins.join(',')}`;
      
      script.onload = () => {
        this.isLoaded = true;
        resolve();
      };
      
      script.onerror = () => {
        reject(new Error('地图 SDK 加载失败'));
      };
      
      document.head.appendChild(script);
    });
    
    return this.loadPromise;
  }
  
  // 初始化地图
  async initMap(containerId, options = {}) {
    try {
      await this.loadSDK();
      
      const defaultOptions = {
        zoom: 10,
        center: [116.397428, 39.90923],
        viewMode: '3D',
        pitch: 0,
        rotation: 0,
        ...options
      };
      
      this.map = new AMap.Map(containerId, defaultOptions);
      
      return {
        success: true,
        map: this.map
      };
      
    } catch (error) {
      console.error('地图初始化失败:', error);
      return {
        success: false,
        error: error.message
      };
    }
  }
  
  // 添加标记
  addMarker(options) {
    if (!this.map) {
      throw new Error('地图未初始化');
    }
    
    const marker = new AMap.Marker({
      position: options.position,
      title: options.title,
      icon: options.icon,
      ...options
    });
    
    this.map.add(marker);
    
    return marker;
  }
  
  // 添加信息窗口
  addInfoWindow(marker, content) {
    if (!this.map) {
      throw new Error('地图未初始化');
    }
    
    const infoWindow = new AMap.InfoWindow({
      content: content,
      offset: new AMap.Pixel(0, -30)
    });
    
    marker.on('click', () => {
      infoWindow.open(this.map, marker.getPosition());
    });
    
    return infoWindow;
  }
  
  // 获取当前位置
  async getCurrentPosition() {
    try {
      await this.loadSDK();
      
      return new Promise((resolve, reject) => {
        AMap.plugin('AMap.Geolocation', () => {
          const geolocation = new AMap.Geolocation({
            enableHighAccuracy: true,
            timeout: 10000,
            maximumAge: 0,
            convert: true,
            showButton: true,
            buttonPosition: 'LB',
            buttonOffset: new AMap.Pixel(10, 20),
            showMarker: true,
            showCircle: true,
            panToLocation: true,
            zoomToAccuracy: true
          });
          
          geolocation.getCurrentPosition((status, result) => {
            if (status === 'complete') {
              resolve({
                success: true,
                position: {
                  longitude: result.position.lng,
                  latitude: result.position.lat
                },
                address: result.formattedAddress,
                accuracy: result.accuracy
              });
            } else {
              reject(new Error(result.message));
            }
          });
        });
      });
      
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
  
  // 地理编码(地址转坐标)
  async geocode(address) {
    try {
      await this.loadSDK();
      
      return new Promise((resolve, reject) => {
        AMap.plugin('AMap.Geocoder', () => {
          const geocoder = new AMap.Geocoder({
            city: '全国'
          });
          
          geocoder.getLocation(address, (status, result) => {
            if (status === 'complete' && result.geocodes.length > 0) {
              const location = result.geocodes[0].location;
              resolve({
                success: true,
                position: {
                  longitude: location.lng,
                  latitude: location.lat
                },
                formattedAddress: result.geocodes[0].formattedAddress
              });
            } else {
              reject(new Error('地理编码失败'));
            }
          });
        });
      });
      
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
  
  // 逆地理编码(坐标转地址)
  async reverseGeocode(longitude, latitude) {
    try {
      await this.loadSDK();
      
      return new Promise((resolve, reject) => {
        AMap.plugin('AMap.Geocoder', () => {
          const geocoder = new AMap.Geocoder();
          const lnglat = new AMap.LngLat(longitude, latitude);
          
          geocoder.getAddress(lnglat, (status, result) => {
            if (status === 'complete' && result.regeocode) {
              resolve({
                success: true,
                address: result.regeocode.formattedAddress,
                addressComponent: result.regeocode.addressComponent
              });
            } else {
              reject(new Error('逆地理编码失败'));
            }
          });
        });
      });
      
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
  
  // 路径规划
  async planRoute(start, end, type = 'driving') {
    try {
      await this.loadSDK();
      
      const routeTypes = {
        driving: 'AMap.Driving',
        walking: 'AMap.Walking',
        transit: 'AMap.Transfer',
        riding: 'AMap.Riding'
      };
      
      const pluginName = routeTypes[type];
      if (!pluginName) {
        throw new Error('不支持的路径规划类型');
      }
      
      return new Promise((resolve, reject) => {
        AMap.plugin(pluginName, () => {
          const routeService = new AMap[type.charAt(0).toUpperCase() + type.slice(1)]({
            map: this.map,
            panel: 'panel'
          });
          
          routeService.search(start, end, (status, result) => {
            if (status === 'complete') {
              resolve({
                success: true,
                routes: result.routes,
                distance: result.routes[0].distance,
                duration: result.routes[0].time
              });
            } else {
              reject(new Error('路径规划失败'));
            }
          });
        });
      });
      
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
  
  // 销毁地图
  destroy() {
    if (this.map) {
      this.map.destroy();
      this.map = null;
    }
  }
}

// 导出高德地图 SDK
 export default AmapSDK;

9.4 自定义编译配置

9.4.1 条件编译

平台条件编译

// utils/platformUtils.js
class PlatformUtils {
  // 获取当前平台
  static getCurrentPlatform() {
    // #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
    
    // #ifdef MP-BAIDU
    return 'mp-baidu';
    // #endif
    
    // #ifdef MP-TOUTIAO
    return 'mp-toutiao';
    // #endif
    
    // #ifdef MP-QQ
    return 'mp-qq';
    // #endif
    
    return 'unknown';
  }
  
  // 检查是否为 App 平台
  static isApp() {
    // #ifdef APP-PLUS
    return true;
    // #endif
    return false;
  }
  
  // 检查是否为 H5 平台
  static isH5() {
    // #ifdef H5
    return true;
    // #endif
    return false;
  }
  
  // 检查是否为小程序平台
  static isMiniProgram() {
    // #ifdef MP
    return true;
    // #endif
    return false;
  }
  
  // 获取平台特定的 API
  static getPlatformAPI() {
    const platform = this.getCurrentPlatform();
    
    switch (platform) {
      case 'app':
        return {
          // App 特定 API
          openLocation: this.openLocationApp,
          getLocation: this.getLocationApp,
          scanCode: this.scanCodeApp
        };
        
      case 'h5':
        return {
          // H5 特定 API
          openLocation: this.openLocationH5,
          getLocation: this.getLocationH5,
          scanCode: this.scanCodeH5
        };
        
      case 'mp-weixin':
        return {
          // 微信小程序特定 API
          openLocation: this.openLocationWX,
          getLocation: this.getLocationWX,
          scanCode: this.scanCodeWX
        };
        
      default:
        return {
          openLocation: () => Promise.reject(new Error('平台不支持')),
          getLocation: () => Promise.reject(new Error('平台不支持')),
          scanCode: () => Promise.reject(new Error('平台不支持'))
        };
    }
  }
  
  // App 平台打开位置
  static openLocationApp(latitude, longitude, name) {
    // #ifdef APP-PLUS
    return new Promise((resolve, reject) => {
      uni.openLocation({
        latitude,
        longitude,
        name,
        success: resolve,
        fail: reject
      });
    });
    // #endif
  }
  
  // H5 平台打开位置
  static openLocationH5(latitude, longitude, name) {
    // #ifdef H5
    const url = `https://uri.amap.com/marker?position=${longitude},${latitude}&name=${encodeURIComponent(name)}`;
    window.open(url, '_blank');
    return Promise.resolve();
    // #endif
  }
  
  // 微信小程序打开位置
  static openLocationWX(latitude, longitude, name) {
    // #ifdef MP-WEIXIN
    return new Promise((resolve, reject) => {
      wx.openLocation({
        latitude,
        longitude,
        name,
        success: resolve,
        fail: reject
      });
    });
    // #endif
  }
}

export default PlatformUtils;

环境条件编译

// config/environment.js
class Environment {
  constructor() {
    this.env = this.getCurrentEnvironment();
    this.config = this.getEnvironmentConfig();
  }
  
  // 获取当前环境
  getCurrentEnvironment() {
    // #ifdef NODE_ENV === 'development'
    return 'development';
    // #endif
    
    // #ifdef NODE_ENV === 'testing'
    return 'testing';
    // #endif
    
    // #ifdef NODE_ENV === 'production'
    return 'production';
    // #endif
    
    return 'development';
  }
  
  // 获取环境配置
  getEnvironmentConfig() {
    const configs = {
      development: {
        apiBaseUrl: 'http://localhost:3000/api',
        debug: true,
        logLevel: 'debug',
        enableMock: true,
        enableVConsole: true
      },
      testing: {
        apiBaseUrl: 'https://test-api.example.com/api',
        debug: true,
        logLevel: 'info',
        enableMock: false,
        enableVConsole: true
      },
      production: {
        apiBaseUrl: 'https://api.example.com/api',
        debug: false,
        logLevel: 'error',
        enableMock: false,
        enableVConsole: false
      }
    };
    
    return configs[this.env] || configs.development;
  }
  
  // 获取 API 基础 URL
  getApiBaseUrl() {
    return this.config.apiBaseUrl;
  }
  
  // 检查是否为开发环境
  isDevelopment() {
    return this.env === 'development';
  }
  
  // 检查是否为生产环境
  isProduction() {
    return this.env === 'production';
  }
  
  // 检查是否启用调试
  isDebugEnabled() {
    return this.config.debug;
  }
  
  // 获取日志级别
  getLogLevel() {
    return this.config.logLevel;
  }
  
  // 初始化环境特定功能
  initEnvironmentFeatures() {
    // 开发环境特定初始化
    // #ifdef NODE_ENV === 'development'
    this.initDevelopmentFeatures();
    // #endif
    
    // 生产环境特定初始化
    // #ifdef NODE_ENV === 'production'
    this.initProductionFeatures();
    // #endif
  }
  
  // 初始化开发环境功能
  initDevelopmentFeatures() {
    // #ifdef NODE_ENV === 'development'
    console.log('开发环境初始化');
    
    // 启用 vConsole
    if (this.config.enableVConsole) {
      this.enableVConsole();
    }
    
    // 启用 Mock 数据
    if (this.config.enableMock) {
      this.enableMockData();
    }
    // #endif
  }
  
  // 初始化生产环境功能
  initProductionFeatures() {
    // #ifdef NODE_ENV === 'production'
    console.log('生产环境初始化');
    
    // 禁用控制台输出
    this.disableConsole();
    
    // 启用错误监控
    this.enableErrorMonitoring();
    // #endif
  }
  
  // 启用 vConsole
  enableVConsole() {
    // #ifdef H5
    import('vconsole').then(VConsole => {
      new VConsole.default();
    });
    // #endif
  }
  
  // 启用 Mock 数据
  enableMockData() {
    // Mock 数据逻辑
    console.log('Mock 数据已启用');
  }
  
  // 禁用控制台输出
  disableConsole() {
    // #ifdef NODE_ENV === 'production'
    console.log = () => {};
    console.warn = () => {};
    console.error = () => {};
    // #endif
  }
  
  // 启用错误监控
  enableErrorMonitoring() {
    // 错误监控逻辑
    window.addEventListener('error', (event) => {
      // 上报错误
      this.reportError(event.error);
    });
  }
  
  // 上报错误
  reportError(error) {
    // 错误上报逻辑
    console.error('错误上报:', error);
  }
}

// 创建环境实例
const environment = new Environment();

// 初始化环境功能
environment.initEnvironmentFeatures();

export default environment;

9.4.2 自定义构建脚本

Webpack 自定义配置

// vue.config.js
const path = require('path');
const webpack = require('webpack');

module.exports = {
  // 基础路径
  publicPath: process.env.NODE_ENV === 'production' ? '/app/' : '/',
  
  // 输出目录
  outputDir: 'dist',
  
  // 静态资源目录
  assetsDir: 'static',
  
  // 生产环境是否生成 sourceMap
  productionSourceMap: false,
  
  // 开发服务器配置
  devServer: {
    port: 8080,
    open: true,
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        pathRewrite: {
          '^/api': '/api'
        }
      }
    }
  },
  
  // 链式操作配置
  chainWebpack: config => {
    // 设置别名
    config.resolve.alias
      .set('@', path.resolve(__dirname, 'src'))
      .set('@components', path.resolve(__dirname, 'src/components'))
      .set('@utils', path.resolve(__dirname, 'src/utils'))
      .set('@assets', path.resolve(__dirname, 'src/assets'));
    
    // 优化图片
    config.module
      .rule('images')
      .test(/\.(png|jpe?g|gif|svg)$/)
      .use('url-loader')
      .loader('url-loader')
      .options({
        limit: 8192,
        name: 'img/[name].[hash:8].[ext]'
      });
    
    // 优化字体
    config.module
      .rule('fonts')
      .test(/\.(woff2?|eot|ttf|otf)$/)
      .use('url-loader')
      .loader('url-loader')
      .options({
        limit: 8192,
        name: 'fonts/[name].[hash:8].[ext]'
      });
    
    // 生产环境优化
    if (process.env.NODE_ENV === 'production') {
      // 代码分割
      config.optimization.splitChunks({
        chunks: 'all',
        cacheGroups: {
          vendor: {
            name: 'chunk-vendors',
            test: /[\\/]node_modules[\\/]/,
            priority: 10,
            chunks: 'initial'
          },
          common: {
            name: 'chunk-common',
            minChunks: 2,
            priority: 5,
            chunks: 'initial',
            reuseExistingChunk: true
          }
        }
      });
      
      // 压缩配置
      config.optimization.minimizer('terser').tap(args => {
        args[0].terserOptions.compress.drop_console = true;
        args[0].terserOptions.compress.drop_debugger = true;
        return args;
      });
    }
  },
  
  // 配置 webpack
  configureWebpack: config => {
    // 定义全局变量
    config.plugins.push(
      new webpack.DefinePlugin({
        'process.env.VUE_APP_VERSION': JSON.stringify(process.env.npm_package_version),
        'process.env.VUE_APP_BUILD_TIME': JSON.stringify(new Date().toISOString())
      })
    );
    
    // 开发环境配置
    if (process.env.NODE_ENV === 'development') {
      config.devtool = 'eval-source-map';
    }
    
    // 生产环境配置
    if (process.env.NODE_ENV === 'production') {
      // 外部化依赖
      config.externals = {
        'vue': 'Vue',
        'vue-router': 'VueRouter',
        'vuex': 'Vuex'
      };
      
      // Gzip 压缩
      const CompressionPlugin = require('compression-webpack-plugin');
      config.plugins.push(
        new CompressionPlugin({
          algorithm: 'gzip',
          test: /\.(js|css|html|svg)$/,
          threshold: 8192,
          minRatio: 0.8
        })
      );
    }
  },
  
  // CSS 相关配置
  css: {
    // 是否提取 CSS
    extract: process.env.NODE_ENV === 'production',
    
    // CSS source map
    sourceMap: false,
    
    // CSS 预处理器配置
    loaderOptions: {
      scss: {
        additionalData: `@import "@/styles/variables.scss";`
      },
      less: {
        additionalData: `@import "@/styles/variables.less";`
      }
    }
  },
  
  // PWA 配置
  pwa: {
    name: 'UniApp Demo',
    themeColor: '#4DBA87',
    msTileColor: '#000000',
    appleMobileWebAppCapable: 'yes',
    appleMobileWebAppStatusBarStyle: 'black',
    
    // 配置 workbox 插件
    workboxPluginMode: 'InjectManifest',
    workboxOptions: {
      swSrc: 'src/sw.js'
    }
  }
};

自定义构建脚本

// scripts/build.js
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');

class BuildScript {
  constructor() {
    this.startTime = Date.now();
    this.platforms = ['h5', 'mp-weixin', 'mp-alipay', 'app-plus'];
    this.environments = ['development', 'testing', 'production'];
  }
  
  // 执行构建
  async build(options = {}) {
    const {
      platform = 'h5',
      environment = 'production',
      clean = true,
      analyze = false
    } = options;
    
    try {
      console.log(chalk.blue('🚀 开始构建...'));
      console.log(chalk.gray(`平台: ${platform}`));
      console.log(chalk.gray(`环境: ${environment}`));
      
      // 清理输出目录
      if (clean) {
        await this.cleanOutput(platform);
      }
      
      // 设置环境变量
      this.setEnvironmentVariables(environment);
      
      // 执行构建命令
      await this.executeBuild(platform, environment);
      
      // 分析构建结果
      if (analyze) {
        await this.analyzeBuild(platform);
      }
      
      // 生成构建报告
      await this.generateBuildReport(platform, environment);
      
      const duration = Date.now() - this.startTime;
      console.log(chalk.green(`✅ 构建完成! 耗时: ${duration}ms`));
      
    } catch (error) {
      console.error(chalk.red('❌ 构建失败:'), error.message);
      process.exit(1);
    }
  }
  
  // 清理输出目录
  async cleanOutput(platform) {
    console.log(chalk.yellow('🧹 清理输出目录...'));
    
    const outputDirs = {
      'h5': 'dist/build/h5',
      'mp-weixin': 'dist/build/mp-weixin',
      'mp-alipay': 'dist/build/mp-alipay',
      'app-plus': 'dist/build/app-plus'
    };
    
    const outputDir = outputDirs[platform];
    if (outputDir && fs.existsSync(outputDir)) {
      execSync(`rm -rf ${outputDir}`, { stdio: 'inherit' });
    }
  }
  
  // 设置环境变量
  setEnvironmentVariables(environment) {
    process.env.NODE_ENV = environment;
    process.env.UNI_PLATFORM = platform;
    process.env.VUE_APP_ENV = environment;
    process.env.VUE_APP_BUILD_TIME = new Date().toISOString();
  }
  
  // 执行构建命令
  async executeBuild(platform, environment) {
    console.log(chalk.blue('📦 执行构建...'));
    
    const buildCommands = {
      'h5': 'uni build --platform h5',
      'mp-weixin': 'uni build --platform mp-weixin',
      'mp-alipay': 'uni build --platform mp-alipay',
      'app-plus': 'uni build --platform app-plus'
    };
    
    const command = buildCommands[platform];
    if (!command) {
      throw new Error(`不支持的平台: ${platform}`);
    }
    
    execSync(command, { stdio: 'inherit' });
  }
  
  // 分析构建结果
  async analyzeBuild(platform) {
    console.log(chalk.blue('📊 分析构建结果...'));
    
    // 使用 webpack-bundle-analyzer
    if (platform === 'h5') {
      execSync('npx webpack-bundle-analyzer dist/build/h5/static/js/*.js', {
        stdio: 'inherit'
      });
    }
  }
  
  // 生成构建报告
  async generateBuildReport(platform, environment) {
    console.log(chalk.blue('📋 生成构建报告...'));
    
    const report = {
      platform,
      environment,
      buildTime: new Date().toISOString(),
      duration: Date.now() - this.startTime,
      version: process.env.npm_package_version,
      nodeVersion: process.version,
      files: this.getOutputFiles(platform)
    };
    
    const reportPath = `dist/build-report-${platform}-${environment}.json`;
    fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
    
    console.log(chalk.green(`📄 构建报告已生成: ${reportPath}`));
  }
  
  // 获取输出文件信息
  getOutputFiles(platform) {
    const outputDirs = {
      'h5': 'dist/build/h5',
      'mp-weixin': 'dist/build/mp-weixin',
      'mp-alipay': 'dist/build/mp-alipay',
      'app-plus': 'dist/build/app-plus'
    };
    
    const outputDir = outputDirs[platform];
    if (!outputDir || !fs.existsSync(outputDir)) {
      return [];
    }
    
    const files = [];
    const walkDir = (dir) => {
      const items = fs.readdirSync(dir);
      
      items.forEach(item => {
        const fullPath = path.join(dir, item);
        const stat = fs.statSync(fullPath);
        
        if (stat.isDirectory()) {
          walkDir(fullPath);
        } else {
          files.push({
            path: path.relative(outputDir, fullPath),
            size: stat.size,
            modified: stat.mtime
          });
        }
      });
    };
    
    walkDir(outputDir);
    return files;
  }
  
  // 构建所有平台
  async buildAll(environment = 'production') {
    console.log(chalk.blue('🚀 构建所有平台...'));
    
    for (const platform of this.platforms) {
      await this.build({ platform, environment });
    }
    
    console.log(chalk.green('✅ 所有平台构建完成!'));
  }
}

// 命令行接口
if (require.main === module) {
  const args = process.argv.slice(2);
  const buildScript = new BuildScript();
  
  const options = {};
  
  // 解析命令行参数
  args.forEach((arg, index) => {
    if (arg === '--platform' && args[index + 1]) {
      options.platform = args[index + 1];
    }
    if (arg === '--env' && args[index + 1]) {
      options.environment = args[index + 1];
    }
    if (arg === '--analyze') {
      options.analyze = true;
    }
    if (arg === '--all') {
      options.buildAll = true;
    }
  });
  
  // 执行构建
  if (options.buildAll) {
    buildScript.buildAll(options.environment);
  } else {
    buildScript.build(options);
  }
}

module.exports = BuildScript;

9.5 本章总结

本章深入探讨了 UniApp 的进阶开发技巧,涵盖了以下重要内容:

学习要点回顾

  1. 原生插件开发

    • 插件项目结构和配置
    • Android 插件开发(Java)
    • iOS 插件开发(Objective-C)
    • JavaScript 桥接层实现
  2. 混合开发模式

    • 原生容器集成
    • Android WebView 容器
    • iOS WKWebView 容器
    • JavaScript 桥接通信
  3. 第三方 SDK 集成

    • 支付 SDK(微信支付、支付宝支付)
    • 地图 SDK(高德地图)
    • SDK 封装和抽象
  4. 自定义编译配置

    • 条件编译技巧
    • 环境配置管理
    • Webpack 自定义配置
    • 自定义构建脚本

实践练习

  1. 开发原生插件

    • 创建一个相机插件
    • 实现文件操作插件
    • 开发设备信息插件
  2. 集成第三方服务

    • 集成推送服务
    • 集成统计分析
    • 集成社交分享
  3. 优化构建流程

    • 配置多环境构建
    • 实现自动化部署
    • 优化打包体积

常见问题解答

Q: 如何选择原生插件开发还是 JavaScript 实现? A: 考虑以下因素: - 功能复杂度:复杂的原生功能建议使用原生插件 - 性能要求:高性能需求选择原生插件 - 平台兼容性:需要跨平台一致性选择 JavaScript - 开发成本:JavaScript 开发成本较低

Q: 混合开发中如何处理原生和 Web 的数据同步? A: 建议方案: - 使用事件机制进行通信 - 建立统一的数据格式 - 实现数据缓存和同步机制 - 处理异步操作和错误情况

Q: 如何优化第三方 SDK 的加载性能? A: 优化策略: - 按需加载 SDK - 使用 CDN 加速 - 实现 SDK 缓存 - 异步初始化

Q: 条件编译如何处理复杂的业务逻辑? A: 最佳实践: - 抽象平台差异到工具类 - 使用策略模式处理平台逻辑 - 保持代码的可读性和维护性 - 充分测试各平台功能

最佳实践建议

  1. 插件开发

    • 遵循平台开发规范
    • 提供完整的错误处理
    • 编写详细的文档
    • 进行充分的测试
  2. SDK 集成

    • 选择稳定可靠的 SDK
    • 实现统一的接口抽象
    • 处理版本兼容性
    • 监控 SDK 性能
  3. 构建优化

    • 合理配置构建环境
    • 实现自动化流程
    • 优化构建性能
    • 监控构建质量
  4. 代码管理

    • 使用版本控制
    • 实现代码审查
    • 建立编码规范
    • 持续集成部署

通过本章的学习,你已经掌握了 UniApp 的高级开发技巧,能够处理复杂的开发场景,构建高质量的跨平台应用。这些技能将帮助你在实际项目中解决各种技术挑战,提升开发效率和应用质量。


恭喜你完成了 UniApp 跨平台开发教程的全部学习! 🎉

从基础入门到进阶技巧,你已经系统地掌握了 UniApp 开发的各个方面。现在你可以:

  • 熟练使用 UniApp 开发跨平台应用
  • 掌握组件开发和复用技巧
  • 进行性能优化和调试
  • 完成应用的打包发布
  • 运用进阶开发技巧解决复杂问题

继续实践和探索,你将成为一名优秀的跨平台开发工程师! const platform = uni.getSystemInfoSync().platform;

if (platform === 'android' || platform === 'ios') {
  return 'APP';
} else {
  return 'MWEB';
}

}

// 生成随机字符串 generateNonceStr() { return Math.random().toString(36).substr(2, 15); }

// 生成签名 generateSign(params) { // 1. 参数排序 const sortedKeys = Object.keys(params).sort();

// 2. 拼接参数
const stringA = sortedKeys
  .filter(key => params[key] !== '' && key !== 'sign')
  .map(key => `${key}=${params[key]}`)
  .join('&');

// 3. 拼接密钥
const stringSignTemp = `${stringA}&key=${this.config.apiKey}`;

// 4. MD5 加密并转大写
return this.md5(stringSignTemp).toUpperCase();

}

// MD5 加密 md5(str) { // 这里需要引入 MD5 库或使用 crypto-js // 示例使用 crypto-js const CryptoJS = require(‘crypto-js’); return CryptoJS.MD5(str).toString(); }

// 对象转 XML objectToXml(obj) { let xml = ‘’; for (const key in obj) { if (obj.hasOwnProperty(key)) { xml += <${key}><![CDATA[${obj[key]}]]></${key}>; } } xml += ‘’; return xml; }

// XML 转对象 xmlToObject(xml) { const result = {}; const regex = /<(\w+)><![CDATA[([^]]+)]]><\/\1>/g; let match;

while ((match = regex.exec(xml)) !== null) {
  result[match[1]] = match[2];
}

return result;

}

// 验证支付结果 async verifyPayResult(paymentData) { try { const params = { appid: this.config.appId, mch_id: this.config.merchantId, transaction_id: paymentData.transaction_id, nonce_str: this.generateNonceStr() };

  params.sign = this.generateSign(params);

  const response = await uni.request({
    url: 'https://api.mch.weixin.qq.com/pay/orderquery',
    method: 'POST',
    header: {
      'Content-Type': 'application/xml'
    },
    data: this.objectToXml(params)
  });

  const result = this.xmlToObject(response.data);

  return {
    success: result.return_code === 'SUCCESS' && result.result_code === 'SUCCESS',
    data: result
  };

} catch (error) {
  return {
    success: false,
    error: error.message
  };
}

} }

// 导出微信支付类 export default WechatPay;

// 使用示例 // const wechatPay = new WechatPay({ // appId: ‘your_app_id’, // merchantId: ‘your_merchant_id’, // apiKey: ‘your_api_key’, // notifyUrl: ‘https://your-domain.com/notify’ // }); // // const payResult = await wechatPay.pay({ // orderNo: ‘ORDER_20231201_001’, // amount: 99.99, // description: ‘商品购买’ // }); “`

支付宝支付集成

”`javascript // utils/alipay.js class Alipay { constructor(config) { this.config = { appId: config.appId, privateKey: config.privateKey, publicKey: config.publicKey, notifyUrl: config.notifyUrl, returnUrl: config.returnUrl, signType: ‘RSA2’, charset: ‘utf-8’, version: ‘1.0’, …config }; }

// 发起支付 async pay(orderInfo) { try { const platform = uni.getSystemInfoSync().platform;

  if (platform === 'android' || platform === 'ios') {
    return this.appPay(orderInfo);
  } else {
    return this.webPay(orderInfo);
  }

} catch (error) {
  console.error('支付宝支付失败:', error);
  return {
    success: false,
    error: error.message
  };
}

}

// App 支付 async appPay(orderInfo) { const bizContent = { out_trade_no: orderInfo.orderNo, total_amount: orderInfo.amount.toString(), subject: orderInfo.subject, body: orderInfo.body, product_code: ‘QUICK_MSECURITY_PAY’ };

const params = {
  app_id: this.config.appId,
  method: 'alipay.trade.app.pay',
  charset: this.config.charset,
  sign_type: this.config.signType,
  timestamp: this.formatDate(new Date()),
  version: this.config.version,
  notify_url: this.config.notifyUrl,
  biz_content: JSON.stringify(bizContent)
};

// 生成签名
params.sign = this.generateSign(params);

// 构建支付参数字符串
const orderString = this.buildOrderString(params);

return new Promise((resolve, reject) => {
  uni.requestPayment({
    provider: 'alipay',
    orderInfo: orderString,
    success: (res) => {
      resolve({
        success: true,
        message: '支付成功',
        data: res
      });
    },
    fail: (err) => {
      reject(new Error(err.errMsg || '支付失败'));
    }
  });
});

}

// Web 支付 async webPay(orderInfo) { const bizContent = { out_trade_no: orderInfo.orderNo, total_amount: orderInfo.amount.toString(), subject: orderInfo.subject, body: orderInfo.body, product_code: ‘FAST_INSTANT_TRADE_PAY’ };

const params = {
  app_id: this.config.appId,
  method: 'alipay.trade.page.pay',
  charset: this.config.charset,
  sign_type: this.config.signType,
  timestamp: this.formatDate(new Date()),
  version: this.config.version,
  notify_url: this.config.notifyUrl,
  return_url: this.config.returnUrl,
  biz_content: JSON.stringify(bizContent)
};

// 生成签名
params.sign = this.generateSign(params);

// 构建支付 URL
const payUrl = 'https://openapi.alipay.com/gateway.do?' + this.buildOrderString(params);

// 跳转到支付页面
window.location.href = payUrl;

return {
  success: true,
  message: '正在跳转到支付页面'
};

}