uni-app 混合式开发指南:APP、小程序一键多平台部署
引言
Uni-app 是一个基于 Vue.js 的跨平台框架,允许开发者编写一套代码,发布到 iOS、Android、Web 以及各种小程序(微信、支付宝、百度、头条、QQ、钉钉等)平台。
1. 环境准备
在开始开发之前,我们需要准备好开发环境。Uni-app 使用 HBuilderX 作为官方推荐的开发工具。
安装 HBuilderX
你可以从 官网下载并安装最新版本的 HBuilderX。
安装 Node.js
Uni-app 需要 Node.js 环境,你可以从 官网下载并安装最新版本的 Node.js。
2. 创建 Uni-app 项目
安装好开发工具后,我们可以开始创建 Uni-app 项目。
使用 HBuilderX 创建项目
- 打开 HBuilderX,点击菜单栏的
File
->New
->Project
。 - 在弹出的窗口中选择
Uni-app Project
,然后点击Next
。 - 输入项目名称和项目路径,然后点击
Finish
。
初始化项目
创建好项目后,我们需要初始化项目,安装必要的依赖。
bash
npm install
3. 项目结构介绍
Uni-app 项目的结构如下:
your-project/
├── common/ # 公共模块
├── components/ # 组件
├── pages/ # 页面
│ ├── index/ # 首页
│ │ ├── index.vue # 首页视图
│ │ └── index.js # 首页逻辑
├── static/ # 静态资源
├── utils/ # 工具函数
├── manifest.json # 应用配置
└── main.js # 入口文件
4. h5+app
使用 uni-app 打包成安卓或者 iOS,具体业务使用 h5 项目进行开发,利用 webview 进行安卓与 h5 之间的通信,以便调用安卓的 API 或者 iOS 的 API。
- uni-app 负责安卓 APK 的开发并提供调用设备功能的一些 API。
- h5 负责开发业务逻辑,使用 uni-app 提供的 API 进行调用安卓或者 iOS 的 API。
5. uni-app webview 部分
html
<template>
<web-view
id="web-view"
class="uni-flex-item"
:src="src"
:webview-styles="webview_styles"
:horizontalScrollBarAccess="horizontalScrollBarAccess"
:verticalScrollBarAccess="verticalScrollBarAccess"
@message="message"
@error="error"
@loading="loading"
@load="load"
@download="download"
@touchstart="touchstart"
@tap="tap"
>
</web-view>
</template>
<script>
import { useHandleMessage } from "@/utssdk/useHandleMessage";
export default {
data() {
return {
webview_styles: { progress: { color: "#00FF00" } },
verticalScrollBarAccess: false,
horizontalScrollBarAccess: false,
src: "http://192.168.110.224:9989",
webviewElement: null,
webviewContext: null,
};
},
onLoad() {},
onReady() {
this.webviewContext = uni.createWebviewContext("web-view", this);
this.webviewElement = uni.getElementById("web-view");
},
methods: {
load() {},
loading() {},
download() {},
touchstart() {},
error() {},
tap() {},
message: (event) => {
const res = event.detail.data[0];
let messageData = useHandleMessage();
let handleMessage = messageData.get("handleMessage");
let data = handleMessage(res);
this.webviewContext?.evalJS(
`_receiveAndoidData(${JSON.stringify(data)})`
);
},
},
};
</script>
<style></style>
安卓 API 封装
typescript
export function useHandleMessage() {
return {
handleMessage: function (e) {
let key = e.get("key");
let params = e.get("params");
let data = {
success: true,
result: {},
};
switch (key) {
case "openUrl":
console.log("other-Page", params);
break;
case "camera":
uni.chooseImage({
count: 9,
sizeType: ["sizeType"],
sourceType: ["album", "camera"],
success: (res) => {
data.result = { data: res.tempFilePaths };
},
});
break;
default:
break;
}
return data;
},
};
}
h5 与安卓之间的通信
html
<!DOCTYPE html>
<html>
<head>
<title>H5与安卓通信</title>
</head>
<body>
<script type="text/javascript">
!(function (e, n) {
"object" == typeof exports && "undefined" != typeof module
? (module.exports = n())
: "function" == typeof define && define.amd
? define(n)
: ((e = e || self).uni = n());
})(this, function () {
"use strict";
try {
var e = {};
Object.defineProperty(e, "passive", {
get: function () {
!0;
},
}),
window.addEventListener("test-passive", null, e);
} catch (e) {}
var n = Object.prototype.hasOwnProperty;
function i(e, i) {
return n.call(e, i);
}
var t = [];
function o() {
return window.__dcloud_weex_postMessage || window.__dcloud_weex_;
}
function a() {
return window.__uniapp_x_postMessage || window.__uniapp_x_;
}
var r = function (e, n) {
var i = { options: { timestamp: +new Date() }, name: e, arg: n };
if (a()) {
if ("postMessage" === e) {
var r = { data: n };
return window.__uniapp_x_postMessage
? window.__uniapp_x_postMessage(r)
: window.__uniapp_x_.postMessage(JSON.stringify(r));
}
var d = {
type: "WEB_INVOKE_APPSERVICE",
args: { data: i, webviewIds: t },
};
window.__uniapp_x_postMessage
? window.__uniapp_x_postMessageToService(d)
: window.__uniapp_x_.postMessageToService(JSON.stringify(d));
} else if (o()) {
if ("postMessage" === e) {
var s = { data: [n] };
return window.__dcloud_weex_postMessage
? window.__dcloud_weex_postMessage(s)
: window.__dcloud_weex_.postMessage(JSON.stringify(s));
}
var w = {
type: "WEB_INVOKE_APPSERVICE",
args: { data: i, webviewIds: t },
};
window.__dcloud_weex_postMessage
? window.__dcloud_weex_postMessageToService(w)
: window.__dcloud_weex_.postMessageToService(JSON.stringify(w));
} else {
if (!window.plus)
return window.parent.postMessage(
{ type: "WEB_INVOKE_APPSERVICE", data: i, pageId: "" },
"*"
);
if (0 === t.length) {
var u = plus.webview.currentWebview();
if (!u)
throw new Error("plus.webview.currentWebview() is undefined");
var g = u.parent(),
v = "";
(v = g ? g.id : u.id), t.push(v);
}
if (plus.webview.getWebviewById("__uniapp__service"))
plus.webview.postMessageToUniNView(
{
type: "WEB_INVOKE_APPSERVICE",
args: { data: i, webviewIds: t },
},
"__uniapp__service"
);
else {
var c = JSON.stringify(i);
plus.webview
.getLaunchWebview()
.evalJS(
'UniPlusBridge.subscribeHandler("'
.concat("WEB_INVOKE_APPSERVICE", '",')
.concat(c, ",")
.concat(JSON.stringify(t), ");")
);
}
}
},
d = {
navigateTo: function () {
var e =
arguments.length > 0 && void 0 !== arguments[0]
? arguments[0]
: {},
n = e.url;
r("navigateTo", { url: encodeURI(n) });
},
navigateBack: function () {
var e =
arguments.length > 0 && void 0 !== arguments[0]
? arguments[0]
: {},
n = e.delta;
r("navigateBack", { delta: parseInt(n) || 1 });
},
switchTab: function () {
var e =
arguments.length > 0 && void 0 !== arguments[0]
? arguments[0]
: {},
n = e.url;
r("switchTab", { url: encodeURI(n) });
},
reLaunch: function () {
var e =
arguments.length > 0 && void 0 !== arguments[0]
? arguments[0]
: {},
n = e.url;
r("reLaunch", { url: encodeURI(n) });
},
redirectTo: function () {
var e =
arguments.length > 0 && void 0 !== arguments[0]
? arguments[0]
: {},
n = e.url;
r("redirectTo", { url: encodeURI(n) });
},
getEnv: function (e) {
a()
? e({ uvue: !0 })
: o()
? e({ nvue: !0 })
: window.plus
? e({ plus: !0 })
: e({ h5: !0 });
},
postMessage: function () {
var e =
arguments.length > 0 && void 0 !== arguments[0]
? arguments[0]
: {};
r("postMessage", e.data || {});
},
},
s = /uni-app/i.test(navigator.userAgent),
w = /Html5Plus/i.test(navigator.userAgent),
u = /complete|loaded|interactive/;
var g =
window.my &&
navigator.userAgent.indexOf(
["t", "n", "e", "i", "l", "C", "y", "a", "p", "i", "l", "A"]
.reverse()
.join("")
) > -1;
var v =
window.swan &&
window.swan.webView &&
/swan/i.test(navigator.userAgent);
var c =
window.qq &&
window.qq.miniProgram &&
/QQ/i.test(navigator.userAgent) &&
/miniProgram/i.test(navigator.userAgent);
var p =
window.tt &&
window.tt.miniProgram &&
/toutiaomicroapp/i.test(navigator.userAgent);
var _ =
window.wx &&
window.wx.miniProgram &&
/micromessenger/i.test(navigator.userAgent) &&
/miniProgram/i.test(navigator.userAgent);
var m = window.qa && /quickapp/i.test(navigator.userAgent);
var f =
window.ks &&
window.ks.miniProgram &&
/micromessenger/i.test(navigator.userAgent) &&
/miniProgram/i.test(navigator.userAgent);
var l =
window.tt &&
window.tt.miniProgram &&
/Lark|Feishu/i.test(navigator.userAgent);
var E =
window.jd &&
window.jd.miniProgram &&
/micromessenger/i.test(navigator.userAgent) &&
/miniProgram/i.test(navigator.userAgent);
var x =
window.xhs &&
window.xhs.miniProgram &&
/xhsminiapp/i.test(navigator.userAgent);
for (
var S,
h = function () {
(window.UniAppJSBridge = !0),
document.dispatchEvent(
new CustomEvent("UniAppJSBridgeReady", {
bubbles: !0,
cancelable: !0,
})
);
},
y = [
function (e) {
if (s || w)
return (
window.__uniapp_x_postMessage ||
window.__uniapp_x_ ||
window.__dcloud_weex_postMessage ||
window.__dcloud_weex_
? document.addEventListener("DOMContentLoaded", e)
: window.plus && u.test(document.readyState)
? setTimeout(e, 0)
: document.addEventListener("plusready", e),
d
);
},
function (e) {
if (_)
return (
window.WeixinJSBridge && window.WeixinJSBridge.invoke
? setTimeout(e, 0)
: document.addEventListener("WeixinJSBridgeReady", e),
window.wx.miniProgram
);
},
function (e) {
if (c)
return (
window.QQJSBridge && window.QQJSBridge.invoke
? setTimeout(e, 0)
: document.addEventListener("QQJSBridgeReady", e),
window.qq.miniProgram
);
},
function (e) {
if (g) {
document.addEventListener("DOMContentLoaded", e);
var n = window.my;
return {
navigateTo: n.navigateTo,
navigateBack: n.navigateBack,
switchTab: n.switchTab,
reLaunch: n.reLaunch,
redirectTo: n.redirectTo,
postMessage: n.postMessage,
getEnv: n.getEnv,
};
}
},
function (e) {
if (v)
return (
document.addEventListener("DOMContentLoaded", e),
window.swan.webView
);
},
function (e) {
if (p)
return (
document.addEventListener("DOMContentLoaded", e),
window.tt.miniProgram
);
},
function (e) {
if (m) {
window.QaJSBridge && window.QaJSBridge.invoke
? setTimeout(e, 0)
: document.addEventListener("QaJSBridgeReady", e);
var n = window.qa;
return {
navigateTo: n.navigateTo,
navigateBack: n.navigateBack,
switchTab: n.switchTab,
reLaunch: n.reLaunch,
redirectTo: n.redirectTo,
postMessage: n.postMessage,
getEnv: n.getEnv,
};
}
},
function (e) {
if (f)
return (
window.WeixinJSBridge && window.WeixinJSBridge.invoke
? setTimeout(e, 0)
: document.addEventListener("WeixinJSBridgeReady", e),
window.ks.miniProgram
);
},
function (e) {
if (l)
return (
document.addEventListener("DOMContentLoaded", e),
window.tt.miniProgram
);
},
function (e) {
if (E)
return (
window.JDJSBridgeReady && window.JDJSBridgeReady.invoke
? setTimeout(e, 0)
: document.addEventListener("JDJSBridgeReady", e),
window.jd.miniProgram
);
},
function (e) {
if (x) return window.xhs.miniProgram;
},
function (e) {
return document.addEventListener("DOMContentLoaded", e), d;
},
],
M = 0;
M < y.length && !(S = y[M](h));
M++
);
S || (S = {});
var P = "undefined" != typeof uni ? uni : {};
if (!P.navigateTo) for (var b in S) i(S, b) && (P[b] = S[b]);
return (P.webView = S), P;
});
</script>
<script type="text/javascript">
function getUrlParams(e) {
if (!e) return "";
var t = {},
r = [],
n = "",
a = "";
try {
var i = [];
if (
(e.indexOf("?") >= 0 &&
(i = e.substring(e.indexOf("?") + 1, e.length).split("&")),
i.length > 0)
)
for (var o in i)
(n = (r = i[o].split("="))[0]), (a = r[1]), (t[n] = a);
} catch (s) {
t = {};
}
return t;
}
window.localStorage.setItem(
"android_env",
JSON.stringify(getUrlParams(window.location.href))
);
</script>
</body>
</html>
安卓调用安卓 API 统一封装
typescript
// webView.ts
export const postMessage = async (key = "", params = {}) => {
const data = await new Promise((resolve) => {
window._receiveAndoidData = (data) => {
resolve(data); // 等待 data 的值返回
};
// 传递参数到 postMessage
uni.webView?.postMessage({
data: {
key: key,
params: {
...params,
},
},
});
});
return data; // 返回 data
};
调用示例
typescript
const postInfo = async () => {
let data = await postMessage("openUrl", {});
alert(JSON.stringify(data));
};
结语
自己对前端开发很热爱,平时除了工作,也希望能把技能用在更多地方,如果有合适的机会也很乐意尝试。如果你有任何问题或建议,欢迎在下方留言,我会尽快回复你。感谢你的阅读!