uni-app WebView 上傳入口通知
本教學說明如何在 uni-app 的 <web-view> 中嵌入獨立訪客頁,並在訪客點擊附件入口時
接收一則通知。適用於 uni-app App-vue 與 App-nvue 專案。
這則通知只表示「上傳入口已被點擊」。檔案選擇、檔案讀取與上傳仍由獨立訪客頁負責, 宿主不得因此再次開啟選擇器,也不得將它當成系統權限結果。
最小接入步驟
- 在
<web-view>中載入獨立訪客頁,並確認頁面已提供約定的window.webUni.postMessage適配器。 - App-vue 繫結
@message,App-nvue 依目標基座繫結@onPostMessage。 - 校驗並記錄
visitor-upload-click,保持宿主被動,不要因此再次開啟選擇器或申請權限。
接入後的時序
訪客點擊附件入口
→ 獨立頁傳送 { type: "visitor-upload-click" }
→ 獨立頁繼續執行 input.click()
→ WebView / 系統開啟檔案選擇器
→ 獨立頁處理 change 與上傳
使用直接訪客頁連結,並替換實際網域與 APP_ID。若要隱藏訪客頁左上角返回按鈕,可以
追加 source=webview:
https://page.visitor-chat.com/direct/{你的APP_ID}?source=webview
固定協議
獨立頁透過約定的 window.webUni 適配器傳送訊息,必須保留 data 外層封裝:
window.webUni?.postMessage({
data: { type: 'visitor-upload-click' },
});
預設 payload 必須是:
{"type":"visitor-upload-click"}
App-vue 通常在 event.detail.data 中收到 payload,部分基座會再包裝成陣列。訊息不傳輸
檔案、憑證、上傳結果或系統權限狀態。permission 僅為前向相容保留,當前附件入口不會
傳送;若保留校驗,只允許 microphone、camera、album,未知值直接忽略。
這是盡力而為的同步事件,不需要 ACK。Bridge 不存在或傳送異常時,獨立頁仍必須繼續 自己的檔案選擇流程。
App-vue 接入
在 WebView 上繫結 @message,先校驗未知資料再消費:
<template>
<web-view :src="visitorUrl" @message="handleWebViewMessage" />
</template>
<script setup lang="ts">
const visitorUrl = 'https://page.visitor-chat.com/direct/YOUR_APP_ID?source=webview';
type UploadPermission = 'microphone' | 'camera' | 'album';
interface UploadClickMessage {
type: 'visitor-upload-click';
permission?: UploadPermission;
}
const permissions = new Set<UploadPermission>([
'microphone',
'camera',
'album',
]);
function isUploadClickMessage(value: unknown): value is UploadClickMessage {
if (!value || typeof value !== 'object') return false;
const candidate = value as Record<string, unknown>;
return candidate.type === 'visitor-upload-click' &&
(candidate.permission === undefined ||
(typeof candidate.permission === 'string' &&
permissions.has(candidate.permission as UploadPermission)));
}
function normalizeMessages(value: unknown): unknown[] {
if (Array.isArray(value)) {
return value.flatMap((entry) => normalizeMessages(entry));
}
if (value && typeof value === 'object' && !('type' in value) && 'data' in value) {
return normalizeMessages((value as { data: unknown }).data);
}
return [value];
}
function handleWebViewMessage(event: { detail?: { data?: unknown } }) {
const messages = normalizeMessages(event.detail?.data);
for (const message of messages) {
if (!isUploadClickMessage(message)) continue;
// 僅記錄或更新狀態,不要呼叫 uni.chooseImage/chooseFile。
console.info('[visitor] upload click', message);
}
}
</script>
event.detail.data 可能是單一物件,也可能是陣列,具體取決於 uni-app 基座版本;範例
專案也會拆開額外的 data 封裝。應相容這些形式,忽略空值與未知 type,不要用寬泛的
型別斷言代替校驗。
App-nvue 接入
部分 App-nvue 基座使用即時事件 @onPostMessage:
下面的處理函式重用上一節 App-vue 範例中的 isUploadClickMessage 與
normalizeMessages。
<template>
<web-view
:src="visitorUrl"
style="flex: 1"
@onPostMessage="handleNvueWebViewMessage"
/>
</template>
<script setup lang="ts">
function handleNvueWebViewMessage(event: { detail?: unknown }) {
const detail = event?.detail;
const rawData =
detail && typeof detail === 'object' && 'data' in detail
? (detail as { data?: unknown }).data ?? detail
: detail;
for (const message of normalizeMessages(rawData)) {
if (isUploadClickMessage(message)) {
console.info('[visitor] upload click', message);
}
}
}
</script>
事件名稱、payload 位置與即時性會受 uni-app、HBuilderX 及除錯基座版本影響。請以目標
版本的元件型別提示與裝置實測為準;H5 瀏覽器結果不能證明 App-Plus 行為。如果目標
App-vue 基座只在頁面後退、銷毀或分享等生命週期事件觸發 @message,不要把它當作點擊
級即時通道,應改用該基座支援的即時訊息 API 或改用 App-nvue 接入。
準備 window.webUni
webUni 是專案約定的頁面側適配器,不是瀏覽器或 uni-app 自動提供的全域 API。請與獨立
訪客頁接入方確認以下一種方式:
- App 基座在頁面可互動前注入帶有
postMessage的物件; - 頁面載入自託管的 DCloud 官方 WebView SDK,在
UniAppJSBridgeReady後為其 API 建立別名。
適配器範例(SDK 路徑僅為範例):
<script src="/vendor/uni.webview.1.5.8.js"></script>
<script>
(() => {
function install() {
if (typeof window.webUni?.postMessage === 'function') return;
const postMessage =
window.uni?.postMessage ?? window.uni?.webView?.postMessage;
if (typeof postMessage !== 'function') return;
window.webUni = {
postMessage: postMessage.bind(
window.uni?.postMessage ? window.uni : window.uni?.webView,
),
};
}
document.addEventListener('UniAppJSBridgeReady', install, { once: true });
install();
})();
</script>
已有 webUni 時不要覆蓋,也不要同時實作兩套傳送邏輯;生產 App-Plus 不要把
window.parent.postMessage fallback 當作通訊協議。如果使用 HTML5+ evalJS 或
appendJsFile 注入腳本,只允許注入已核准的獨立訪客網域。evalJS() 返回不代表頁面已
安裝適配器,應使用頁面 ready 日誌或受控聯調訊息確認。
Bridge 就緒前發生的點擊可能無法通知宿主,但獨立頁仍必須開啟自己的檔案輸入。如果要求 每次點擊都可達,應明確延遲啟用附件入口或設計有界佇列,不能從本協議推導出來。
安全與容錯
- 設定
src前只允許已核准的 HTTPS 訪客網域,並繼續校驗後續頁面跳轉。 - App-vue 回調通常沒有瀏覽器
event.origin,因此 App-Plus 主要依賴 URL 與導覽白名單 隔離訊息來源。 - H5 模擬還應校驗
event.origin與event.source;這只是瀏覽器 fixture,不是 App-Plus 生產通訊方案。 - 解析失敗時忽略訊息,只記錄不含敏感資料的診斷資訊。
- 使用者取消選擇屬於正常分支,不應輸出上傳錯誤。
- 一次真實點擊最多消費一則通知;收到通知後絕不能再次開啟選擇器或申請權限。
本地模擬與驗收
uniapp-demo 範例專案包含 App-vue 頁面、本地 mock 訪客頁、payload 歸一化、來源校驗與
訊息記錄。進入該專案目錄執行:
pnpm install
./node_modules/.bin/uni --host 127.0.0.1 --port 5188
開啟命令輸出的 H5 位址,點擊 mock 訪客頁中的「選擇附件並通知宿主」。預期結果:
- 宿主新增一則
visitor-upload-click記錄; - 預設 JSON 不包含
permission; - mock 訪客頁仍執行 HTML
<input type="file">; - 取消選擇不會產生錯誤,也不會撤回已傳送的通知。
H5 載入真實訪客頁時可以設定:
VITE_VISITOR_URL="https://page.visitor-chat.com/direct/YOUR_APP_ID?source=webview" \
./node_modules/.bin/uni --host 127.0.0.1 --port 5188
建置 App-Plus:
./node_modules/.bin/uni build -p app-plus
public/mock-visitor.html 由 H5 開發伺服器提供,App-Plus 建置不會自動將它作為本地頁
打包。App-Plus 聯調請設定可存取的訪客 URL,或依專案規則將本地 HTML 放入
hybrid/html / static,並確認目標基座可以載入。
發佈前請在目標 iOS 與 Android 真機上驗證,並記錄系統、HBuilderX、除錯基座版本、回調 到達時機、選擇器行為與導覽白名單結果。H5 建置結果不能取代真機證據。
未涵蓋範圍
本協議不定義 Native 代替檔案選擇或上傳、相機/相簿/麥克風權限申請、上傳進度或取消、ACK
以及通用 Native ↔ WebView Bridge。需要這些能力時請另行設計協議,不要擴充
visitor-upload-click 的語義。