405 lines
12 KiB
Markdown
405 lines
12 KiB
Markdown
# 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. **保障层**:统一通信格式、配置域名白名单、处理登录态同步,确保混合开发模式稳定运行。
|