移动端 H5
界面与原生交互
1、介绍
原生和H5
交互,主要是为了将原生中的用户信息、权限校验等信息传递给H5
,不需要H5
重复去登录,默前移动端用到webViewjavascripBridge
这个混编框架,方便H5
进行一些基础的原生操作。WebViewJavascriptBridge
是移动UIView
和Html
交互通信的桥梁,用白话来说就是实现java(ios为oc)和js的互相调用的桥梁。替代了WebView
的自带的JavascriptInterface
的接口,使得我们的开发更加灵活和安全。
参考源码:源码地址
WebViewJavascriptBridge
:Git
地址
全屏显示
微应用
URL
地址后面带上 关键字:http://10.62.16.95/demofront/web-js?isFallScreen=true
JS
代码中:created() { this.clickCallNative('full_screen', '1'); //隐藏标题,全面屏 this.clickCallNative('capsule_mode', '1');//隐藏胶囊 }
2、使用
2.1 H5集成桥接
下载参考源码:源码地址;
导入
bridge.js
文件,并引入到需要使用的界面;Bridge.call
:H5
发消息给原生;Bridge.register
:监听原生发送的消息;
2.2 H5
发消息给原生
key
:发送的类型;(3.0 key类型介绍 )
params
:发送给原生的数据。
//import Bridge from "@/api/bridge.js";
Bridge.call(key, params, (data) => {
//部分调用,可以直接监听到原生数据
let json = JSON.parse(data);
this.refresh_token = json.token;
});
2.3 原生返回数据给H5
keys
:需要监听的类型,数组;
type
:原生发送时的类型,和监听类型一致;
data
:原生发送的数据,字符串类型。
//import Bridge from "@/api/bridge.js";
initListener() {
let keys = [
"init_pai_data",
"refresh_token",
"selected_depart",
"selected_person",
"selected_park",
"selected_card",
];
Bridge.register(keys, (type, data, responseCallback) => {
let json = JSON.parse(data);
if (type == "init_pai_data") {
this.msgJson = data;
this.info = json;
} else if (type == "selected_depart") {
this.parseDepartData(json);
} else if (type == "selected_person") {
this.parsePersonData(json);
} else if (type == "selected_park") {
this.parseParkData(json);
} else if (type == "selected_card") {
this.selected_card = data;
}
if (responseCallback) responseCallback(data);
});
}
2.4 示例
示例1:支付组件
详细说明:链接
//import Bridge from "@/api/bridge.js";
let info = {
suiteId: "1283242540845371392",
parkId: this.$flyrise.get(this.$sp.parkId),
bizType:"ordinary",//orderType 和 bizType,二选一
orderType: "4",//充值订单
orderVO: {
amount: curMoney, //支付金额(分)
goodsName: title, //支付标题
payeeId: this.$flyrise.get(this.$sp.userId) //收款账户
}
}
Bridge.call("open_pay", info, (data) => {
let json = JSON.parse(data);
this.res = data;
});
示例2:修改用户信息
//import Bridge from "@/api/bridge.js";
//统一字符串类型:用户头像、昵称、性别('1':男,'2':女)、邮箱
let params={
userHead:"",
nickName:"晓",
sex:"2",
email:"123@qq.com"
}
Bridge.call("update_user_info", params, (data) => {
let json = JSON.parse(data);
this.res = data;
if(json.isUpdate){
//成功
}else{
//失败
}
});
示例3:刷新界面
//import Bridge from "@/api/bridge.js";
let params={
refreshType:"my" //main:首页(第一个页面); my:我的(最后一个页面)
}
Bridge.call("refresh_view", params, (data) => {
let json = JSON.parse(data);
this.res = data;
});
//import Bridge from "@/api/bridge.js";
let params={
refreshType:"my" //main:首页(第一个页面); my:我的(最后一个页面)
}
Bridge.call("refresh_view", params, (data) => {
let json = JSON.parse(data);
this.res = data;
});
示例4:快捷支付
//import Bridge from "@/api/bridge.js";
let info = {
bizOrderNo:"", //支付订单号,非必填
payInfo:"", //支付订单,必填
payMethod:"" //付款方式:02:微信;03:支付宝。必填
}
Bridge.call("quick_pay", info, (data) => {
let json = JSON.parse(data);
this.res = data;
});
返回参数
// payStatus:订单交易状态,1成功,其它失败;
// bizOrderNo:支付订单号;
// payMethod:付款方式。
json = {
payStatus:"1",
bizOrderNo:"109089878978",
payMethod:"02"
}
示例5:获取定位
//import Bridge from "@/api/bridge.js";
Bridge.call("start_location", {}, (data) => {
let json = JSON.parse(data);
this.res = data;
});
返回参数
json = {
"accuracy":30,
"address":"广东省珠海市香洲区港湾大道1号靠近南方软件园",
"city":"珠海市",
"cityCode":"0756",
"latitude":22.370807,
"locationType":5,
"longitude":113.572739
}
示例6:返回上一页
解决跳过支付空白页的事件
Bridge.call("back_page", {}, () => {
});
示例7:打开附件
打开附件
let data = {
"type":"local", //不传:是用第三方应用打开; local:本地打开;
"url":"" //可传完整地址或附近Id
}
Bridge.call("download_attach", data, () => {
});
3、参数介绍
3.1 关于token
token传递分为两种
init_pai_data
方式,在界面初始化成功后一次性传递所有用户信息,包含token;url
的params
传递,传递的token字段名为st
,并且token
经过加密,需要解密后使用;完整
token
:Bearer 9cece910-06b8-489e-a6ff-66e76af8a0c4
3.1.1 token
解密方法一
//1. package.json 中引入中台小程序基础库
// "@flyriselink/pai-mp-core": "^1.0.1"
npm install @flyriselink/pai-mp-core
//2. main.js 中初始化
import flyriseCore from "@flyriselink/pai-mp-core";
Vue.use(flyriseCore)
//3. 解密后,手动拼装 `stHead`
uni.$p.decodeToken(st)
//let token = stHead + uni.$p.decodeToken(st)
3.1.2 token
解密方法二
完整token
:Bearer 9cece910-06b8-489e-a6ff-66e76af8a0c4
//1. 导入第三方加解密库
npm install crypto-js
import CryptoJS from "crypto-js";
//2. 解密后,手动拼装 `stHead`
const decodeToken = (data) => {
let reverse = data.split('').reverse().join('')
return CryptoJS.enc.Base64.parse(reverse).toString(CryptoJS.enc.Utf8)
}
//let token = stHead + decodeToken(st)
3.2 原生支持的类型
监听:被动接收原生返回的数据;
点击:主动调用原生的事件。
字段 | 返回类型 | 介绍 | 使用 |
---|---|---|---|
init_pai_data |
json |
原生默认传递的数据 | 监听 |
refresh_token |
json |
刷新Token 事件 |
监听、触发 |
selected_depart |
json |
选择部门事件 | 监听、触发 |
selected_person |
json |
选择人员事件 | 监听、触发 |
selected_park |
json |
选择园区事件 | 监听、触发 |
selected_card |
json |
选择名片事件 | 监听、触发 |
full_screen |
string |
隐藏标题(0:显示,1:隐藏) | 触发 |
finish_web_view |
string |
关闭H5 界面 |
触发 |
capsule_mode |
json |string |
胶囊模式 (0:显示,1:灯光模式,2:暗黑模式,3:隐藏模式) | 触发 |
open_pay |
string |
调用凌云支付组件 | 监听、触发 |
init_param |
string |
列表传参给详情页 | 监听 |
status_bar_mode |
string |
1:下沉白色,2:下沉黑色,3:沉侵白色,4:沉侵暗黑 | 触发 |
open_micro |
string |
打开微应用 | 触发 |
open_scan |
string |
打开扫码 | 监听、触发 |
close_app |
string |
关闭App (HOME键退出) |
触发 |
open_native |
string |
打开原生部分功能 | 触发 |
update_user_info |
string |
修改用户信息 | 监听、触发 |
refresh_view |
string |
刷新界面,main:首页;my:我都 | 触发 |
back_page |
string |
返回上一页,其中解决支付跳过空白页事件 | 触发 |
download_attach |
string |
附件下载并打开 | 触发 |
3.3 init_pai_data
原生默认传递给
H5
的用户信息数据
参数 | 类型 | 介绍 |
---|---|---|
token |
string |
用户唯一值(带前缀的) |
userName |
string |
用户名 |
name |
string |
昵称、公司员工为真实姓名 |
phone |
string |
手机号 |
userId |
string |
用户 Id |
entId |
string |
企业 Id |
entName |
string |
企业名称 |
tokenValue |
string |
唯一值(不带前缀的) |
parkId : |
string |
园区 Id |
parkName |
string |
园区名称 |
3.4 url
参数控制
也可以在
url
上直接添加scr
来达到全屏显示效果,可避免状态栏闪屏效果。
https://www.baidu.com?scr=1
参数名 | 类型 | 描述 |
---|---|---|
sta |
1:下沉白色,2:下沉黑色,3:沉侵白色,4:沉侵暗黑 | 控制状态栏(默认:3) |
cap |
0:显示,1:灯光模式,2:暗黑模式,3:隐藏模式 | 控制胶囊(默认:0) |
scr |
0:显示,1:隐藏 | 隐藏标题栏(默认:0) |
pro |
0:显示,1:隐藏 | 隐藏进度条(默认:0) |
3.5 打开微应用
open_micro
,参数中必须包含必填,选填为自定义参数,原生会回传给小程序:biz
参数名 | 类型 | 描述 | 是否必填 |
---|---|---|---|
pageAppId |
string |
页面唯一值,微应用AppCode |
是 |
url |
string |
路径 | 是 |
… | string |
否 |
3.6 打开原生功能
open_native
,参数为json
格式
itemCode
:需要开启的原生界面类型
url
:打开微应用、web
页面时需要用到的参数
appId
:开启微应用时使用的页面标识
{
itemCode:"",
url:"",
appId:""
}
itemCode参数说明
itemCode值 | 类型 | 描述 | |
---|---|---|---|
1001 |
String |
uniApp启动 | |
1014 |
String |
选择企业 | |
1015 |
String |
应用消息 | |
1016 |
String |
全文检索 | |
1017 |
String |
工作台设置界面 | |
1018 |
String |
工作台企业app,B和P端微应用 | |
1019 |
String |
工作台企业app,B端微应用 | |
1020 |
String |
工作台企业app,P端微应用 | |
1021 |
String |
工作台企业app,C端微应用 | |
1030 |
String |
通用扫码 | |
1031 |
String |
用户详情 | |
1032 |
String |
我的设置 |
4、H5
完整代码
4.1 bridge.js
代码
安卓和
iOS
通用的库,直接使用即可。
发送和监听的核心库,使用方式:
1、新建bridge.js
,将代码考入;
2、引入到需要调用原生和监听原生的界面使用即可,详情参考完整的Vue
代码。
call
:给原生发送消息:
name
:发送给原生的key
,字符串类型,用来区分事件类型;
data
:发送给原生的参数,用来传递数据;
callback
:用来监听原生的回调。
register
:接收原生发送的消息:
names
:用来监听原生返回的key
,数组类型,可以监听多个类型;
callback
:监听原生返回的数据。
//判断机型
let u = navigator.userAgent;
function setupWebViewJavascriptBridge(callback) {
//判断ios 还是Android
if (/(iPhone|iPad|iPod|iOS)/i.test(u)) {
console.log("-->>>调用-ios")
connectWebViewJavascriptBridgeIOS(callback)
} else {
console.log("-->>>调用-安卓")
connectWebViewJavascriptBridgeAndroid(callback)
}
}
//苹果注册事件监听
function connectWebViewJavascriptBridgeIOS(callback) {
if (window.WebViewJavascriptBridge) {
return callback(window.WebViewJavascriptBridge)
}
if (window.WVJBCallbacks) {
return window.WVJBCallbacks.push(callback)
}
window.WVJBCallbacks = [callback]
let WVJBIframe = document.createElement('iframe')
WVJBIframe.style.display = 'none'
WVJBIframe.src = 'https://__bridge_loaded__'
document.documentElement.appendChild(WVJBIframe)
setTimeout(() => {
document.documentElement.removeChild(WVJBIframe)
}, 0)
}
//安卓注册事件监听
function connectWebViewJavascriptBridgeAndroid(callback) {
if (window.WebViewJavascriptBridge) {
callback(WebViewJavascriptBridge)
} else {
document.addEventListener(
'WebViewJavascriptBridgeReady',
function () {
callback(WebViewJavascriptBridge)
},
false
);
}
}
export default {
call(name, data, callback) {
setupWebViewJavascriptBridge(function (bridge) {
console.log("-->>>call-2:", name)
bridge.callHandler(name, data, callback)
})
},
register(names, callback) {
setupWebViewJavascriptBridge(function (bridge) {
if (!/(iPhone|iPad|iPod|iOS)/i.test(u)) {
console.log("初始化-安卓")
bridge.init(function (message, responseCallback) {
console.log("-->>>初始化-安卓-成功")
responseCallback("");
});
}
names.forEach(item => {
bridge.registerHandler(item, (data, responseCallback) => {
console.log("-->>>register:", item)
callback(item, data, responseCallback)
});
});
})
}
}
4.2 Vue
界面代码
建议使用中台的前端默认模板来测试,使用的 UI
库是有赞vant
,前端工程默认包含。
vue-json-viewer
:格式化显示json
的一个库,需要开发者自己导入,只是显示数据用的库,非必要;
Bridge
:封装的webViewjavascripBridge
,也就是bridge.js
,监听和发数据的核心库,必须;
Html
代码
<template>
<div class="container">
<van-cell-group title="接收数据">
<json-viewer :value="info" :expand-depth="3" copyable sort></json-viewer>
</van-cell-group>
<div style="height: 16px"></div>
<van-cell-group title="权限校验">
<van-cell title="token值(带前缀的)" :value="info.token" />
<van-cell title="token值" :value="info.tokenValue" />
</van-cell-group>
<div style="height: 16px"></div>
<van-cell-group title="用户信息">
<van-cell title="用户名" :value="info.userName" />
<van-cell title="昵称" :value="info.name" />
<van-cell title="手机号" :value="info.phone" />
<van-cell title="用户 Id" :value="info.userId" />
</van-cell-group>
<div style="height: 16px"></div>
<van-cell-group title="企业信息">
<van-cell title="企业 Id" :value="info.entId" />
<van-cell title="企业名称" :value="info.entName" />
</van-cell-group>
<div style="height: 16px"></div>
<van-cell-group title="园区信息">
<van-cell title="园区 Id" :value="info.parkId" />
<van-cell
title="园区名称"
:value="info.parkName"
is-link
@click="clickCallNative('submit_from_text', info.parkName)"
/>
</van-cell-group>
<div style="height: 26px"></div>
<van-cell-group title="原生交互">
<van-cell
title="刷新 token "
:value="refresh_token"
is-link
@click="clickCallNative('refresh_token', refresh_token)"
/>
<van-cell
title="选择部门"
:value="selected_depart"
is-link
@click="clickCallNative('selected_depart', selected_depart)"
/>
<van-cell
title="选择人员"
:value="selected_person"
is-link
@click="clickCallNative('selected_person', selected_person)"
/>
<van-cell
title="选择园区"
:value="selected_park"
is-link
@click="clickCallNative('selected_park', selected_park)"
/>
<van-cell
title="选择名片"
:value="selected_card"
is-link
@click="clickCallNative('selected_card', selected_card)"
/>
<van-cell
title="全屏显示"
value="灯光模式"
icon="fire-o"
is-link
@click="clickCallNative('full_screen', '1')"
/>
<van-cell
title="全屏显示"
value="暗黑模式"
icon="fire"
is-link
@click="clickCallNative('full_screen', '2')"
/>
<van-cell
title="非全屏显示"
icon="home-o"
is-link
@click="clickCallNative('full_screen', '0')"
/>
<van-cell
title="显示胶囊"
value="灯光模式"
icon="eye-o"
is-link
@click="clickCallNative('capsule_mode', capsuleList1)"
/>
<van-cell
title="显示胶囊"
value="暗黑模式"
icon="eye"
is-link
@click="clickCallNative('capsule_mode', capsuleList2)"
/>
<van-cell
title="隐藏胶囊"
icon="closed-eye"
is-link
@click="clickCallNative('capsule_mode', '{\'type\':\'3\'}')"
/>
<van-cell
title="关闭界面"
icon="close"
is-link
@click="clickCallNative('finish_web_view', '关闭界面')"
/>
</van-cell-group>
<div style="height: 60px"></div>
</div>
</template>
js
代码
<script>
import Bridge from "@/api/bridge.js";
import JsonViewer from "vue-json-viewer";
export default {
head: {
meta: [
{ charset: "utf-8" },
{
name: "viewport",
content:
"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no",
},
],
},
components: { JsonViewer },
data() {
return {
capsuleList1: {
type: "1",
list: [
{ title: "百度", url: "https://www.baidu.com/" },
{ title: "玩安卓", url: "https://wanandroid.com/index" },
],
},
capsuleList2: {
type: "2",
list: [
{ title: "百度", url: "https://www.baidu.com/" },
{ title: "玩安卓", url: "https://wanandroid.com/index" },
],
},
msgJson: {},
refresh_token: "",
selected_depart: "",
selected_person: "",
selected_park: "",
selected_card: "",
info: {
token: "",
userName: "",
name: "",
phone: "",
userId: "",
entId: "",
entName: "",
parkId: "",
parkName: "",
tokenValue: "",
},
};
},
created() {
this.initListener();
this.clickCallNative('full_screen', '0');
this.clickCallNative('capsule_mode', '0');
},
methods: {
initListener() {
let keys = [
"init_pai_data",
"refresh_token",
"selected_depart",
"selected_person",
"selected_park",
"selected_card",
];
Bridge.register(keys, (type, data, responseCallback) => {
let json = JSON.parse(data);
if (type == "init_pai_data") {
this.msgJson = data;
console.log("-->>>data:", data);
this.info = json;
} else if (type == "selected_depart") {
this.parseDepartData(json);
} else if (type == "selected_person") {
this.parsePersonData(json);
} else if (type == "selected_park") {
this.parseParkData(json);
} else if (type == "selected_card") {
this.selected_card = data;
}
if (responseCallback) responseCallback(data);
});
},
parseDepartData(json) {
if (json && json.items) this.selected_depart = json.items[0].name;
},
parsePersonData(json) {
if (json && json.items) this.selected_person = json.items[0].name;
},
parseParkData(item) {
if (item) this.selected_park = item.parkName;
},
clickCallNative(key, params) {
Bridge.call(key, params, (data) => {
if (key == "refresh_token") {
let json = JSON.parse(data);
this.refresh_token = json.token;
}
console.log("-->>>返回数据:", data);
});
},
},
};
</script>
css
代码
<style>
.container {
height: 750rpx;
background-color: rgba(0, 0, 0, 0.02);
}
.response-content {
width: 375rpx;
margin: 16px;
font-size: 16px;
color: rgba(0, 0, 0, 0.65);
}
</style>