feat(mobile-oa-h5): add service-account OAuth bind page for OA notify

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-08-13 19:17:21 +08:00
1 parent 80722d76db
commit dc53c6e871
4 files changed
+193 -1

No files matched your search

+2
View File
@@ -1,3 +1,5 @@
VITE_API_BASE=/api
# 与 web-admin `.env.development` / mobile-h5 LOCAL_MINIO 对齐
VITE_MINIO_BASE_URL=http://47.99.164.66:20004/zkxw
# 微信服务号 AppID(OA 审批通知网页授权绑定;勿放 Secret)
VITE_WX_MP_APPID=
+5
View File
@@ -0,0 +1,5 @@
VITE_API_BASE=/api
VITE_MINIO_BASE_URL=
# 微信服务号 AppID(OA 审批通知网页授权绑定;勿放 Secret)
# 运维须在服务号后台配置网页授权回调域名,并在小程序配置业务域名
VITE_WX_MP_APPID=
+3 -1
View File
@@ -2,6 +2,7 @@ import { createRouter, createWebHistory } from 'vue-router'
import FormSink from './views/FormSink.vue'
import OaStart from './views/OaStart.vue'
import OaDetail from './views/OaDetail.vue'
import OaMpBind from './views/OaMpBind.vue'
export default createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
@@ -12,6 +13,7 @@ export default createRouter({
},
{ path: '/oa-form-sink', component: FormSink },
{ path: '/oa-start', component: OaStart },
{ path: '/oa-detail', component: OaDetail }
{ path: '/oa-detail', component: OaDetail },
{ path: '/oa-mp-bind', component: OaMpBind }
]
})
+183
View File
@@ -0,0 +1,183 @@
<template>
<div class="oa-mp-bind">
<div v-if="phase === 'loading'" class="oa-mp-bind__card">
<van-loading type="spinner" color="#0a9f6d" vertical>处理中…</van-loading>
</div>
<div v-else-if="phase === 'success'" class="oa-mp-bind__card oa-mp-bind__card--ok">
<div class="oa-mp-bind__icon" aria-hidden="true">✓</div>
<p class="oa-mp-bind__title">已开启审批通知</p>
<p class="oa-mp-bind__hint">可返回小程序继续使用</p>
<van-button type="primary" block class="oa-mp-bind__btn" @click="goBack">
返回
</van-button>
</div>
<div v-else class="oa-mp-bind__card oa-mp-bind__card--err">
<p class="oa-mp-bind__title">{{ errorText || '绑定失败' }}</p>
<van-button plain block class="oa-mp-bind__btn" @click="goBack">返回</van-button>
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { showToast } from 'vant'
import { captureTokenFromUrl, getToken } from '../utils/auth.js'
import { post } from '../utils/http.js'
import { closeToHost } from '../oa/close-to-host.js'
const route = useRoute()
/** @type {import('vue').Ref<'loading' | 'success' | 'error'>} */
const phase = ref('loading')
const errorText = ref('')
function readCode() {
const fromRoute = String(route.query.code || '').trim()
if (fromRoute) return fromRoute
try {
return String(new URLSearchParams(window.location.search || '').get('code') || '').trim()
} catch {
return ''
}
}
function stablePageUrl() {
return `${window.location.origin}/oa-h5/oa-mp-bind`
}
function redirectToWechatOAuth(appid) {
const redirectUri = encodeURIComponent(stablePageUrl())
const url =
`https://open.weixin.qq.com/connect/oauth2/authorize` +
`?appid=${encodeURIComponent(appid)}` +
`&redirect_uri=${redirectUri}` +
`&response_type=code&scope=snsapi_base&state=mpbind#wechat_redirect`
window.location.replace(url)
}
function fail(msg) {
errorText.value = msg
phase.value = 'error'
}
function goBack() {
closeToHost(
{ action: 'oa-mp-bind-done', message: phase.value === 'success' ? '已开启审批通知' : '' },
{
showToast: (msg) => showToast(msg),
fallbackMessage: '请关闭本页返回小程序',
fallbackMiniPath: '/pages/index/index'
}
)
}
async function bindWithCode(code) {
try {
await post('/mini/auth/mp-bind', { code })
phase.value = 'success'
// 成功后尝试回到小程序(可选)
try {
closeToHost(
{ action: 'oa-mp-bind-done', message: '已开启审批通知' },
{
showToast: (msg) => showToast(msg),
fallbackMessage: '已开启审批通知',
fallbackMiniPath: '/pages/index/index'
}
)
} catch {
/* ignore host close errors */
}
} catch (e) {
fail((e && e.message) || '绑定失败')
}
}
onMounted(() => {
captureTokenFromUrl()
const code = readCode()
const appid = String(import.meta.env.VITE_WX_MP_APPID || '').trim()
const token = getToken()
if (!code) {
if (!appid) {
fail('未配置服务号 AppID')
return
}
if (!token) {
fail('请从小程序入口打开')
return
}
redirectToWechatOAuth(appid)
return
}
if (!token) {
fail('请从小程序入口打开')
return
}
void bindWithCode(code)
})
</script>
<style scoped>
.oa-mp-bind {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 24px 20px;
box-sizing: border-box;
background: #f5f7f6;
}
.oa-mp-bind__card {
width: 100%;
max-width: 360px;
padding: 32px 24px 24px;
border-radius: 16px;
background: #fff;
text-align: center;
box-shadow: 0 4px 16px rgba(10, 159, 109, 0.08);
}
.oa-mp-bind__icon {
width: 56px;
height: 56px;
margin: 0 auto 16px;
border-radius: 50%;
background: #e9f9f2;
color: #0a9f6d;
font-size: 28px;
line-height: 56px;
font-weight: 600;
}
.oa-mp-bind__title {
margin: 0 0 8px;
font-size: 18px;
font-weight: 600;
color: #0f291e;
line-height: 1.4;
}
.oa-mp-bind__hint {
margin: 0 0 24px;
font-size: 14px;
color: #5f7a6d;
line-height: 1.5;
}
.oa-mp-bind__card--err .oa-mp-bind__title {
margin-bottom: 24px;
color: #b91c1c;
font-size: 16px;
font-weight: 500;
}
.oa-mp-bind__btn {
--van-button-primary-background: #0a9f6d;
--van-button-primary-border-color: #0a9f6d;
border-radius: 10px;
}
</style>