feat(oa): add shared OA webview and detail URL builder

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-08-12 15:59:40 +08:00
1 parent 2dac9d6fbd
commit cf4994ef53
8 files changed
+273

No files matched your search

+48
View File
@@ -56,6 +56,54 @@
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/oa/webview/index",
"style": {
"navigationBarTitleText": "审批详情",
"navigationBarBackgroundColor": "#ecfdf5",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/oa/drafts/index",
"style": {
"navigationBarTitleText": "草稿箱",
"navigationBarBackgroundColor": "#ecfdf5",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/oa/pending/index",
"style": {
"navigationBarTitleText": "待办中心",
"navigationBarBackgroundColor": "#ecfdf5",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/oa/done/index",
"style": {
"navigationBarTitleText": "已办中心",
"navigationBarBackgroundColor": "#ecfdf5",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/oa/mine/index",
"style": {
"navigationBarTitleText": "我发起的",
"navigationBarBackgroundColor": "#ecfdf5",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/oa/cc/index",
"style": {
"navigationBarTitleText": "抄送我的",
"navigationBarBackgroundColor": "#ecfdf5",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/demo/list",
"style": {
+12
View File
@@ -0,0 +1,12 @@
<template>
<view class="page-shell" style="padding:24rpx">
<text>{{ title }}</text>
</view>
</template>
<script>
export default {
data() {
return { title: '抄送我的' }
}
}
</script>
+12
View File
@@ -0,0 +1,12 @@
<template>
<view class="page-shell" style="padding:24rpx">
<text>{{ title }}</text>
</view>
</template>
<script>
export default {
data() {
return { title: '已办中心' }
}
}
</script>
+12
View File
@@ -0,0 +1,12 @@
<template>
<view class="page-shell" style="padding:24rpx">
<text>{{ title }}</text>
</view>
</template>
<script>
export default {
data() {
return { title: '草稿箱' }
}
}
</script>
+12
View File
@@ -0,0 +1,12 @@
<template>
<view class="page-shell" style="padding:24rpx">
<text>{{ title }}</text>
</view>
</template>
<script>
export default {
data() {
return { title: '我发起的' }
}
}
</script>
+12
View File
@@ -0,0 +1,12 @@
<template>
<view class="page-shell" style="padding:24rpx">
<text>{{ title }}</text>
</view>
</template>
<script>
export default {
data() {
return { title: '待办中心' }
}
}
</script>
+131
View File
@@ -0,0 +1,131 @@
<template>
<!-- 通用 OA web-view:发起 + 详情共用;结构对齐 start/webview -->
<web-view v-if="src" :src="src" @message="onMessage" />
</template>
<script>
import {
buildOaDetailUrl,
buildOaStartUrl,
takeOaDetailTitle,
takeOaStartTitle
} from '../../../utils/oa-h5.js'
import { forceRelogin, getToken } 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'
])
export default {
data() {
return { src: '', _closing: false }
},
onLoad(query) {
if (!getToken()) {
forceRelogin('请先登录')
return
}
const instanceId = String((query && query.instanceId) || '')
const taskId = String((query && query.taskId) || '')
const mode = String((query && query.mode) || '') || 'view'
const appId = String((query && query.appId) || '')
const draftId = String((query && query.draftId) || '')
let src = ''
if (instanceId) {
const cachedName = takeOaDetailTitle()
if (cachedName) {
uni.setNavigationBarTitle({ title: cachedName })
}
src = buildOaDetailUrl({ instanceId, taskId, mode })
} else if (draftId || appId) {
const cachedName = takeOaStartTitle()
if (cachedName) {
uni.setNavigationBarTitle({ title: cachedName })
} else if (appId) {
this.resolveTitle(appId)
}
src = buildOaStartUrl({ appId, draftId })
} else {
uni.showToast({ title: '参数错误', icon: 'none' })
setTimeout(() => {
uni.navigateBack({
fail: () => {}
})
}, 400)
return
}
if (!/\btoken=/.test(src)) {
forceRelogin('请先登录')
return
}
console.log('[oa webview] src=', src)
this.src = src
// H5:OA 页在 iframe 内,靠 postMessage 通知本页 uni.navigateBack
// (微信小程序由 OA 内 wx.miniProgram.navigateBack,勿在此再退一层)
// #ifdef H5
this._onParentMessage = (e) => {
const d = e && e.data
if (!d || d.source !== 'zkxw-oa-h5') return
this.navigateBackFromOa(d)
}
window.addEventListener('message', this._onParentMessage)
// #endif
},
onUnload() {
// #ifdef H5
if (this._onParentMessage) {
window.removeEventListener('message', this._onParentMessage)
this._onParentMessage = null
}
// #endif
},
methods: {
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) {
if (this._closing) return
const action = String((payload && payload.action) || '')
if (action && !CLOSE_ACTIONS.has(action)) return
this._closing = true
const msg = String((payload && payload.message) || '').trim()
if (msg) {
uni.showToast({ title: msg, icon: 'none', duration: 1200 })
}
setTimeout(() => {
uni.navigateBack({
fail: () => {
this._closing = false
}
})
}, msg ? 400 : 0)
},
onMessage(e) {
/* 微信:多在后退/销毁时触发;列表页 onShow 可刷新。勿再 navigateBack */
console.log('[oa webview] message', e && e.detail)
}
}
}
</script>
+34
View File
@@ -38,6 +38,17 @@ export function buildOaStartUrl({ appId, draftId } = {}) {
return withQuery('/oa-start', params)
}
/** 审批详情页 URL(须带 token;mode 为 view | approve) */
export function buildOaDetailUrl({ instanceId, taskId, mode } = {}) {
const token = getToken()
const params = {}
if (instanceId) params.instanceId = instanceId
if (taskId) params.taskId = taskId
if (mode) params.mode = mode // view | approve
if (token) params.token = token
return withQuery('/oa-detail', params)
}
const TITLE_KEY = 'oa_start_nav_title'
/** 跳转前缓存流程名(勿把中文放进 navigateTo / web-view URL,微信易白屏) */
@@ -60,3 +71,26 @@ export function takeOaStartTitle() {
return ''
}
}
const DETAIL_TITLE_KEY = 'oa_detail_nav_title'
/** 跳转前缓存详情标题(勿把中文放进 navigateTo / web-view URL) */
export function stashOaDetailTitle(name) {
const t = String(name || '').trim()
try {
if (t) uni.setStorageSync(DETAIL_TITLE_KEY, t)
else uni.removeStorageSync(DETAIL_TITLE_KEY)
} catch (_) {
/* ignore */
}
}
export function takeOaDetailTitle() {
try {
const t = String(uni.getStorageSync(DETAIL_TITLE_KEY) || '').trim()
uni.removeStorageSync(DETAIL_TITLE_KEY)
return t
} catch (_) {
return ''
}
}