Files
zkxw/mobile-h5/pages/oa/webview/index.vue

257 lines
7.5 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<!-- 通用 OA web-view:发起 + 详情共用;结构对齐 start/webview -->
<web-view v-if="src" :src="src" @message="onMessage" />
</template>
<script>
import {
buildOaDetailUrl,
buildOaStartUrl,
resolveOaReturnPath,
takeOaDetailTitle,
takeOaStartTitle
} from '../../../utils/oa-h5.js'
import { navigateBackFromOaHost } from '../../../utils/oa-navigate-back.js'
import {
forceRelogin,
getToken,
silentWechatLogin
} from '../../../utils/auth.js'
import { fetchOaTemplateDetail } from '../../../utils/oa-api.js'
const CLOSE_ACTIONS = new Set([
'oa-start-close',
'oa-start-cancel',
'oa-start-draft',
'oa-start-submitted',
'oa-detail-close',
'oa-detail-done',
'oa-detail-withdraw',
'oa-detail-restart'
])
function buildQueryString(query) {
const q = query || {}
const parts = []
Object.keys(q).forEach((key) => {
const val = q[key]
if (val === undefined || val === null || val === '') return
parts.push(
encodeURIComponent(key) + '=' + encodeURIComponent(String(val))
)
})
return parts.join('&')
}
export default {
data() {
return { src: '', _closing: false, _query: {} }
},
async onLoad(query) {
this._query = query || {}
if (!getToken()) {
try {
await silentWechatLogin()
} catch (e) {
const qs = buildQueryString(this._query)
forceRelogin(
'请先登录',
'/pages/oa/webview/index' + (qs ? '?' + qs : '')
)
return
}
}
this.openFromQuery(this._query)
},
onShow() {
console.log('[oa webview] onShow closing=', this._closing)
},
onUnload() {
console.log('[oa webview] onUnload')
// #ifdef H5
if (this._onParentMessage) {
window.removeEventListener('message', this._onParentMessage)
this._onParentMessage = null
}
// #endif
},
methods: {
openFromQuery(query) {
const instanceId = String((query && query.instanceId) || '')
const taskId = String((query && query.taskId) || '')
const mode = String((query && query.mode) || '') || 'view'
const autoPrint = String((query && query.autoPrint) || '') === '1'
const appId = String((query && query.appId) || '')
const draftId = String((query && query.draftId) || '')
const fromInstanceId = String((query && query.fromInstanceId) || '')
// 传给 OA H5:真机 navigateBack 静默失败时 wx.reLaunch/redirectTo 用
const returnPath = resolveOaReturnPath(query)
let src = ''
if (instanceId) {
const cachedName = takeOaDetailTitle()
if (cachedName) {
uni.setNavigationBarTitle({ title: cachedName })
}
src = buildOaDetailUrl({
instanceId,
taskId,
mode,
autoPrint,
returnPath
})
} else if (draftId || appId || fromInstanceId) {
const cachedName = takeOaStartTitle()
if (cachedName) {
uni.setNavigationBarTitle({ title: cachedName })
} else if (appId) {
this.resolveTitle(appId)
}
src = buildOaStartUrl({ appId, draftId, fromInstanceId, returnPath })
} else {
uni.showToast({ title: '参数错误', icon: 'none' })
setTimeout(() => {
uni.navigateBack({
fail: () => {}
})
}, 400)
return
}
if (!/\btoken=/.test(src)) {
const qs = buildQueryString(query)
forceRelogin(
'请先登录',
'/pages/oa/webview/index' + (qs ? '?' + qs : '')
)
return
}
console.log('[oa webview] onLoad', {
returnPath,
mode,
instanceId,
taskId,
appId,
draftId,
fromInstanceId,
src
})
try {
const pages = getCurrentPages() || []
console.log(
'[oa webview] stack',
pages.map((p) => (p && p.route) || '')
)
} catch (e) {
console.log('[oa webview] stack err', e)
}
this.src = src
// H5:OA 页在 iframe 内,靠 postMessage 通知本页 uni.navigateBack
// (微信小程序由 OA 内 wx.miniProgram.navigateBack,勿在此再退一层)
// #ifdef H5
if (!this._onParentMessage) {
this._onParentMessage = (e) => {
const d = e && e.data
if (!d || d.source !== 'zkxw-oa-h5') return
console.log('[oa webview] iframe message', d)
this.navigateBackFromOa(d)
}
window.addEventListener('message', this._onParentMessage)
}
// #endif
},
async resolveTitle(appId) {
try {
const detail = await fetchOaTemplateDetail(appId)
const name = String(
(detail && (detail.appName || detail.name)) || ''
).trim()
if (name) uni.setNavigationBarTitle({ title: name })
} catch (_) {
/* 保留 pages.json 兜底标题 */
}
},
navigateBackFromOa(payload) {
console.log(
'[oa webview] navigateBackFromOa',
payload,
'closing=',
this._closing
)
if (this._closing) return
const action = String((payload && payload.action) || '')
if (action && !CLOSE_ACTIONS.has(action)) {
console.log('[oa webview] ignore action', action)
return
}
// 复制重开:离开详情 webview 栈时,改开发起页(同页 redirect)
if (action === 'oa-detail-restart') {
const appId = String((payload && payload.appId) || '').trim()
const fromInstanceId = String(
(payload && (payload.fromInstanceId || payload.instanceId)) || ''
).trim()
if (!appId && !fromInstanceId) return
this._closing = true
const q = []
if (appId) q.push('appId=' + encodeURIComponent(appId))
if (fromInstanceId) {
q.push('fromInstanceId=' + encodeURIComponent(fromInstanceId))
}
uni.redirectTo({
url: '/pages/oa/webview/index?' + q.join('&'),
fail: () => {
this._closing = false
uni.navigateTo({
url: '/pages/oa/webview/index?' + q.join('&'),
fail: () => {
this._closing = false
}
})
}
})
return
}
this._closing = true
const msg = String((payload && payload.message) || '').trim()
if (msg) {
uni.showToast({ title: msg, icon: 'none', duration: 1200 })
}
setTimeout(() => {
navigateBackFromOaHost({
onStuck: () => {
this._closing = false
}
})
}, msg ? 400 : 0)
},
onMessage(e) {
// 微信:postMessage 多在 web-view 后退/销毁时送达。
// 若 H5 侧 navigateBack 已成功,此处再退会多退一层,故默认只打日志。
// 若携带需宿主处理的 action(如复制重开),在仍停留本页时兜底处理。
console.log('[oa webview] @message raw', e && e.detail)
const batches = (e && e.detail && e.detail.data) || []
const items = Array.isArray(batches) ? batches : [batches]
for (let i = items.length - 1; i >= 0; i--) {
const raw = items[i]
const payload =
raw && typeof raw === 'object' && raw.data !== undefined
? raw.data
: raw
const action = String((payload && payload.action) || '')
console.log('[oa webview] @message item', i, action, payload)
if (action === 'oa-detail-restart') {
this.navigateBackFromOa(payload)
return
}
}
}
}
}
</script>