# UniApp + H5混合开发微信小程序 技术实施方案（UniApp端）
你希望通过UniApp开发微信小程序，以web-view嵌套H5承载核心业务展示，同时由UniApp原生层负责登录、退出、支付等核心能力，以下是UniApp端的详细技术实施方案，明确需要开发的核心模块、功能和关键逻辑。

## 一、整体架构与职责边界
### 核心分工
| 模块/能力       | UniApp（小程序端）负责                          | H5端负责                          |
|----------------|---------------------------------------------|--------------------------------|
| 用户身份       | 微信登录、token管理、用户信息存储、退出登录逻辑          | 接收并使用用户信息，触发退出登录指令          |
| 支付           | 调起微信支付、支付结果处理、支付状态同步                | 发起支付请求、展示支付结果                |
| 页面容器       | 提供tabBar框架、web-view容器、页面跳转控制            | 承载table栏、业务内容展示、内部页面跳转        |
| 通信           | 接收H5消息、主动向H5推送数据、处理通信异常              | 向UniApp发送指令、接收UniApp数据、展示状态反馈    |

## 二、UniApp端核心开发内容
### 1. 项目基础配置
#### （1）全局配置（pages.json）
配置tabBar、页面路由、web-view权限等核心配置：
```json
{
  "pages": [
    // 登录页
    "pages/login/login",
    // tabBar主页面（嵌套web-view）
    "pages/index/index",
    // 其他tabBar页面（如需）
    "pages/my/my"
  ],
  "tabBar": {
    "color": "#666666",
    "selectedColor": "#007AFF",
    "backgroundColor": "#ffffff",
    "borderStyle": "black",
    "list": [
      {
        "pagePath": "pages/index/index",
        "text": "首页",
        "iconPath": "static/tabbar/home.png",
        "selectedIconPath": "static/tabbar/home-active.png"
      },
      {
        "pagePath": "pages/my/my",
        "text": "我的",
        "iconPath": "static/tabbar/my.png",
        "selectedIconPath": "static/tabbar/my-active.png"
      }
    ]
  },
  "permission": {
    "scope.userLocation": {
      "desc": "你的位置信息将用于小程序定位"
    }
  },
  "webview": {
    "webviewParameter": {
      "allowUniversalAccessFromFileURLs": true,
      "allowFileAccess": true
    }
  }
}
```

#### （2）微信小程序配置（manifest.json）
开启微信小程序相关权限，配置AppID等：
```json
{
  "mp-weixin": {
    "appid": "你的微信小程序AppID",
    "setting": {
      "urlCheck": false, // 允许访问非域名白名单的H5（开发阶段，正式需配置白名单）
      "es6": true,
      "postcss": true
    },
    "usingComponents": true,
    "permission": {
      "scope.userInfo": {
        "desc": "获取你的用户信息用于登录"
      }
    }
  }
}
```

### 2. 核心页面开发
#### （1）登录页（pages/login/login.vue）
负责微信授权登录、获取用户信息、token存储：
```vue
<template>
  <view class="login-container">
    <button open-type="getUserProfile" @click="wxLogin" type="primary">微信快捷登录</button>
  </view>
</template>

<script>
export default {
  methods: {
    async wxLogin() {
      try {
        // 1. 获取微信登录code
        const loginRes = await uni.login({ provider: 'weixin' });
        if (loginRes.code) {
          // 2. 调用后端接口，换取用户token和信息
          const userRes = await uni.request({
            url: '你的后端登录接口', // 如：https://api.xxx.com/wx/login
            method: 'POST',
            data: {
              code: loginRes.code,
              appid: '你的小程序AppID'
            }
          });

          if (userRes.data.code === 200) {
            // 3. 存储用户信息和token（全局可用）
            const { token, userInfo } = userRes.data.data;
            uni.setStorageSync('wx_token', token);
            uni.setStorageSync('user_info', userInfo);
            
            // 4. 登录成功跳转到首页
            uni.switchTab({ url: '/pages/index/index' });
            uni.showToast({ title: '登录成功', icon: 'success' });
          } else {
            uni.showToast({ title: userRes.data.msg, icon: 'none' });
          }
        }
      } catch (err) {
        uni.showToast({ title: '登录失败，请重试', icon: 'none' });
        console.error('登录异常：', err);
      }
    }
  }
};
</script>

<style scoped>
.login-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
}
button {
  width: 80%;
  height: 80rpx;
  font-size: 32rpx;
}
</style>
```

#### （2）tabBar首页（pages/index/index.vue）
核心容器页，嵌套web-view、处理与H5的通信：
```vue
<template>
  <view class="webview-container">
    <!-- web-view组件：嵌套H5页面，绑定通信事件 -->
    <web-view 
      :src="h5Url" 
      @message="handleH5Message"
      id="main-webview"
    ></web-view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      h5Url: '你的H5网页地址', // 如：https://h5.xxx.com/home
      userToken: '',
      userInfo: {}
    };
  },
  onShow() {
    // 页面显示时校验登录态，无登录态则跳转登录页
    this.checkLoginStatus();
    // 拼接用户信息到H5链接（初始化传参）
    this.initH5Url();
  },
  methods: {
    // 1. 校验登录状态
    checkLoginStatus() {
      this.userToken = uni.getStorageSync('wx_token');
      this.userInfo = uni.getStorageSync('user_info');
      if (!this.userToken) {
        uni.redirectTo({ url: '/pages/login/login' });
      }
    },

    // 2. 初始化H5链接（携带用户信息）
    initH5Url() {
      if (this.userToken) {
        // 拼接token、用户ID等关键信息到H5链接
        this.h5Url = `${this.h5Url}?token=${this.userToken}&userId=${this.userInfo.id}&nickName=${encodeURIComponent(this.userInfo.nickName)}`;
      }
    },

    // 3. 接收H5发送的消息（核心通信逻辑）
    handleH5Message(e) {
      // H5发送的消息格式建议：{ type: '指令类型', data: '指令参数' }
      const [message] = e.detail.data;
      if (!message) return;

      switch (message.type) {
        // H5触发支付
        case 'triggerPay':
          this.handlePay(message.data);
          break;
        // H5触发退出登录
        case 'triggerLogout':
          this.handleLogout();
          break;
        // H5请求重新获取用户信息
        case 'getUserInfo':
          this.sendUserInfoToH5();
          break;
        default:
          console.log('未知指令类型：', message.type);
      }
    },

    // 4. 处理支付逻辑（UniApp原生调起微信支付）
    async handlePay(payParams) {
      try {
        // 4.1 调用后端获取微信支付参数（如prepay_id）
        const payRes = await uni.request({
          url: '你的后端支付接口', // 如：https://api.xxx.com/pay/create
          method: 'POST',
          data: {
            token: this.userToken,
            ...payParams // H5传递的订单ID、金额等参数
          }
        });

        if (payRes.data.code !== 200) {
          uni.showToast({ title: payRes.data.msg, icon: 'none' });
          // 通知H5支付失败
          this.sendMsgToH5({ type: 'payFail', data: payRes.data.msg });
          return;
        }

        // 4.2 调起微信支付
        const wxPayParams = payRes.data.data;
        uni.requestPayment({
          timeStamp: wxPayParams.timeStamp,
          nonceStr: wxPayParams.nonceStr,
          package: wxPayParams.package,
          signType: 'MD5',
          paySign: wxPayParams.paySign,
          // 支付成功回调
          success: () => {
            uni.showToast({ title: '支付成功', icon: 'success' });
            this.sendMsgToH5({ type: 'paySuccess', data: '支付成功' });
          },
          // 支付失败/取消回调
          fail: (err) => {
            uni.showToast({ title: '支付失败', icon: 'none' });
            this.sendMsgToH5({ type: 'payFail', data: err.errMsg });
          }
        });
      } catch (err) {
        console.error('支付异常：', err);
        this.sendMsgToH5({ type: 'payFail', data: '支付接口异常' });
      }
    },

    // 5. 处理退出登录
    handleLogout() {
      // 清除本地存储的登录态
      uni.removeStorageSync('wx_token');
      uni.removeStorageSync('user_info');
      // 通知H5退出成功
      this.sendMsgToH5({ type: 'logoutSuccess' });
      // 跳转登录页
      uni.redirectTo({ url: '/pages/login/login' });
    },

    // 6. 主动向H5发送消息
    sendMsgToH5(msg) {
      // 创建web-view上下文，指定ID（与页面中web-view的id一致）
      const webViewCtx = uni.createWebViewContext('main-webview');
      // 发送消息（H5需监听message事件接收）
      webViewCtx.postMessage({ data: msg });
    },

    // 7. 主动向H5推送用户信息
    sendUserInfoToH5() {
      this.sendMsgToH5({
        type: 'userInfo',
        data: {
          token: this.userToken,
          userId: this.userInfo.id,
          nickName: this.userInfo.nickName,
          avatarUrl: this.userInfo.avatarUrl
        }
      });
    }
  }
};
</script>

<style scoped>
.webview-container {
  width: 100%;
  height: 100vh;
}
</style>
```

#### （3）“我的”页面（pages/my/my.vue）
可选，如需原生展示用户信息、退出按钮等：
```vue
<template>
  <view class="my-container">
    <view class="user-info">
      <image :src="userInfo.avatarUrl" class="avatar"></image>
      <text class="nickname">{{ userInfo.nickName }}</text>
    </view>
    <button @click="handleLogout" type="warn">退出登录</button>
  </view>
</template>

<script>
export default {
  data() {
    return {
      userInfo: {}
    };
  },
  onShow() {
    this.userInfo = uni.getStorageSync('user_info');
  },
  methods: {
    handleLogout() {
      uni.removeStorageSync('wx_token');
      uni.removeStorageSync('user_info');
      uni.redirectTo({ url: '/pages/login/login' });
    }
  }
};
</script>

<style scoped>
.my-container {
  padding: 40rpx;
}
.user-info {
  display: flex;
  align-items: center;
  margin-bottom: 60rpx;
}
.avatar {
  width: 120rpx;
  height: 120rpx;
  border-radius: 50%;
  margin-right: 30rpx;
}
.nickname {
  font-size: 36rpx;
  font-weight: bold;
}
button {
  width: 100%;
  height: 80rpx;
}
</style>
```

### 3. 全局工具类（可选）
封装通信、登录校验等通用方法，便于复用：
```js
// common/utils.js
/**
 * 向H5发送消息
 * @param {String} webViewId web-view的ID
 * @param {Object} msg 消息内容
 */
export const sendMsgToH5 = (webViewId, msg) => {
  const webViewCtx = uni.createWebViewContext(webViewId);
  webViewCtx.postMessage({ data: msg });
};

/**
 * 校验登录态
 * @returns {Boolean} 是否已登录
 */
export const checkLogin = () => {
  const token = uni.getStorageSync('wx_token');
  if (!token) {
    uni.redirectTo({ url: '/pages/login/login' });
    return false;
  }
  return true;
};
```

### 4. 关键注意事项
1. **域名白名单配置**：微信小程序后台需配置H5域名、后端接口域名的白名单，否则无法访问；
2. **通信格式统一**：UniApp与H5的通信消息需约定固定格式（如`{type: 'xxx', data: 'xxx'}`），避免解析异常；
3. **支付权限**：确保小程序已开通微信支付权限，后端支付接口需正确对接微信支付商户平台；
4. **登录态同步**：H5页面刷新时，需重新从UniApp获取登录态（可通过URL参数或postMessage）；
5. **兼容性**：web-view组件在微信小程序中部分JS API有限制，需避免在H5中调用小程序专属API（如支付、登录）。

## 总结
UniApp端核心开发内容可归纳为3个关键点：
1. **基础层**：配置tabBar、页面路由、微信小程序权限，搭建整体框架；
2. **核心能力层**：开发登录页实现微信授权登录，在web-view容器页处理与H5的通信，封装支付、退出登录等核心逻辑；
3. **保障层**：统一通信格式、配置域名白名单、处理登录态同步，确保混合开发模式稳定运行。
