Quellcode durchsuchen

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

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu vor 2 Monaten
Ursprung
Commit
cf4994ef53

+ 48 - 0
mobile-h5/pages.json

@@ -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 - 0
mobile-h5/pages/oa/cc/index.vue

@@ -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 - 0
mobile-h5/pages/oa/done/index.vue

@@ -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 - 0
mobile-h5/pages/oa/drafts/index.vue

@@ -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 - 0
mobile-h5/pages/oa/mine/index.vue

@@ -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 - 0
mobile-h5/pages/oa/pending/index.vue

@@ -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 - 0
mobile-h5/pages/oa/webview/index.vue

@@ -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 - 0
mobile-h5/utils/oa-h5.js

@@ -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 ''
+  }
+}