-
+
-
+
-
正在加载合同签署页面...
+
{{ refreshing ? '正在获取签署页面...' : '正在加载合同签署页面...' }}
+
+
+
{{ refreshError }}
+
重新获取链接
@@ -36,29 +41,68 @@
diff --git a/src/pages/certification/index.vue b/src/pages/certification/index.vue
index 935999d..69a97b3 100644
--- a/src/pages/certification/index.vue
+++ b/src/pages/certification/index.vue
@@ -73,7 +73,12 @@
:certification-data="certificationData"
:submit-time="manualReviewSubmitTime"
:company-name="enterpriseForm.companyName"
- @refresh="getCertificationDetails"
+ @refresh="() => getCertificationDetails({ silent: true })"
+ />
+
+
@@ -143,7 +148,9 @@ import ContractSign from './components/ContractSign.vue'
import EnterpriseInfo from './components/EnterpriseInfo.vue'
import EnterpriseVerify from './components/EnterpriseVerify.vue'
import ManualReviewPending from './components/ManualReviewPending.vue'
+import PlatformSelect from './components/PlatformSelect.vue'
const router = useRouter()
+const route = useRoute()
const userStore = useUserStore()
const certificationSteps = [
{
@@ -158,6 +165,12 @@ const certificationSteps = [
description: '等待管理员审核企业信息',
icon: ClockIcon,
},
+ {
+ key: 'platform_select',
+ title: '选择平台',
+ description: '选择 e签宝 或法大大',
+ icon: DocumentTextIcon,
+ },
{
key: 'enterprise_verify',
title: '企业认证',
@@ -222,6 +235,58 @@ const devCurrentStep = ref('enterprise_info')
// 合同签署加载状态
const contractSignLoading = ref(false)
+// 合同签署状态轮询:进入 contract_applied 后定时拉详情,触发后端 checkAndUpdateSignStatus
+// 状态一旦变为 contract_signed / completed 或失败状态立即停止
+let signPollTimer = null
+const SIGN_POLL_INTERVAL = 5000 // 5 秒/次
+const signPolling = ref(false)
+
+const stopSignStatusPolling = () => {
+ if (signPollTimer) {
+ clearTimeout(signPollTimer)
+ signPollTimer = null
+ }
+ signPolling.value = false
+}
+
+const startSignStatusPolling = () => {
+ // 已在轮询则不重复启动
+ if (signPollTimer) return
+ signPolling.value = true
+
+ const tick = async () => {
+ // 兜底:状态已离开 contract_applied 则停止
+ const status = certificationData.value?.status
+ if (status !== 'contract_applied') {
+ stopSignStatusPolling()
+ return
+ }
+ try {
+ // 静默拉详情,GetCertification 内会自动调用后端 checkAndUpdateSignStatus
+ const res = await certificationApi.getCertificationDetails()
+ if (res?.data) {
+ certificationData.value = res.data
+ stepMeta.value = res.data?.metadata || {}
+ const newStatus = res.data?.status
+ // 签署完成或失败:交给 setCurrentStepByStatus 切页,并停止轮询
+ if (newStatus === 'contract_signed' || newStatus === 'completed' ||
+ newStatus === 'contract_rejected' || newStatus === 'contract_expired') {
+ stopSignStatusPolling()
+ await setCurrentStepByStatus()
+ return
+ }
+ }
+ } catch (e) {
+ console.warn('合同签署状态轮询失败:', e)
+ }
+ // 还在轮询则排下一次
+ if (signPolling.value) {
+ signPollTimer = setTimeout(tick, SIGN_POLL_INTERVAL)
+ }
+ }
+ signPollTimer = setTimeout(tick, SIGN_POLL_INTERVAL)
+}
+
// 只补 enterprise_verify 所需 auth_url,不改动原有页面展示数据结构
// 轮询策略:最多 5 秒,每秒 1 次(最多 5 次)
const pollAuthUrlOnly = async (maxTries = 5) => {
@@ -280,6 +345,19 @@ const handleEnterpriseSubmit = async (payload) => {
}
}
+const handlePlatformSelected = async (data) => {
+ if (data) {
+ certificationData.value = data
+ stepMeta.value = data.metadata || {}
+ }
+ await getCertificationDetails()
+ if (stepMeta.value?.auth_url) {
+ currentStep.value = 'enterprise_verify'
+ } else if (certificationData.value) {
+ await setCurrentStepByStatus()
+ }
+}
+
const handleEnterpriseVerifySubmit = async () => {
try {
ElMessage.success('企业认证成功')
@@ -292,10 +370,24 @@ const handleEnterpriseVerifySubmit = async () => {
const handleStartContractSign = async () => {
try {
contractSignLoading.value = true
- // 调用后端接口,发起合同签署
- await certificationApi.applyContract()
+ const res = await certificationApi.applyContract()
+ const embedUrl = res?.data?.contract_sign_url
+ if (embedUrl) {
+ stepMeta.value = {
+ ...(stepMeta.value || {}),
+ contract_sign_url: embedUrl,
+ contract_sign_short_url: res?.data?.contract_sign_short_url || '',
+ }
+ }
ElMessage.success('合同签署流程已启动')
await getCertificationDetails()
+ // details 库内可能是短链,保留刚申请到的 iframe 长链
+ if (embedUrl) {
+ stepMeta.value = {
+ ...(stepMeta.value || {}),
+ contract_sign_url: embedUrl,
+ }
+ }
} catch (error) {
ElMessage.error(error?.message || '发起签署失败,请稍后重试')
} finally {
@@ -355,10 +447,12 @@ const goToProfile = () => {
router.push('/profile')
}
-// 获取认证详情
-const getCertificationDetails = async () => {
+// 获取认证详情(silent=true 时不展示全页 loading,用于人工审核轮询)
+// 返回是否成功,便于调用方在失败时不要把步骤重置到「填写企业信息」
+const getCertificationDetails = async (opts = {}) => {
+ const silent = opts?.silent === true
try {
- loading.value = true
+ if (!silent) loading.value = true
const res = await certificationApi.getCertificationDetails()
certificationData.value = res.data
console.log('s', res)
@@ -391,9 +485,15 @@ const getCertificationDetails = async () => {
// 步骤特定元数据
stepMeta.value = res.data?.metadata || {}
await setCurrentStepByStatus()
+ return true
} catch (error) {
console.error('获取认证详情失败:', error)
ElMessage.error('获取认证详情失败')
+ // 已有数据时保持当前步骤,避免失败时默认落到「填写企业信息」
+ if (certificationData.value) {
+ await setCurrentStepByStatus()
+ }
+ return false
} finally {
loading.value = false
}
@@ -414,20 +514,43 @@ const setCurrentStepByStatus = async () => {
currentStep.value = 'enterprise_info'
break
case 'info_pending_review':
- currentStep.value = 'manual_review'
+ if (certificationData.value?.metadata?.need_select_platform) {
+ currentStep.value = 'platform_select'
+ } else {
+ currentStep.value = 'manual_review'
+ }
break
case 'info_submitted':
- currentStep.value = 'enterprise_verify'
+ if (!certificationData.value?.metadata?.auth_url && certificationData.value?.metadata?.need_select_platform) {
+ currentStep.value = 'platform_select'
+ } else {
+ currentStep.value = 'enterprise_verify'
+ }
break
case 'enterprise_verified':
+ // 无签署任务时不应进入签署合同(创建失败脏数据),留在选平台
+ if (!certificationData.value?.metadata?.esign_flow_id && !certificationData.value?.metadata?.sign_task_ready) {
+ currentStep.value = 'platform_select'
+ } else {
+ currentStep.value = 'contract_sign'
+ }
+ break
case 'contract_applied':
+ currentStep.value = 'contract_sign'
+ // 进入签署中:启动轮询,自动推进到完成/失败
+ startSignStatusPolling()
+ break
case 'contract_rejected':
case 'contract_expired':
currentStep.value = 'contract_sign'
+ // 失败终态:停止轮询
+ stopSignStatusPolling()
break
case 'contract_signed':
case 'completed':
currentStep.value = 'completed'
+ // 完成:停止轮询
+ stopSignStatusPolling()
// 认证完成时重新获取用户信息
if (previousStatus !== 'completed') {
try {
@@ -528,12 +651,37 @@ async function handleIframeMessage(event) {
// 初始化
onMounted(() => {
+ // 若法大大等第三方在 iframe 内回跳到本页,立即顶层打开入驻页
+ if (window.top && window.top !== window.self) {
+ const target = `${window.location.origin}/profile/certification${window.location.search || ''}`
+ window.top.location.href = target
+ return
+ }
+
window.addEventListener('message', handleIframeMessage)
- getCertificationDetails()
+ getCertificationDetails().then(async () => {
+ // 签署完成顶层回跳:推进状态到 completed
+ if (route.query?.signCallback) {
+ try {
+ await certificationApi.confirmSign()
+ await getCertificationDetails()
+ } catch (e) {
+ console.warn('签署回调确认失败,将轮询状态', e)
+ pollCertificationDetails(8)
+ }
+ }
+ })
+
+ // 从法大大 redirect 带回的 query(authResult / clientCorpId 等)时主动同步状态
+ const q = route.query || {}
+ if (q.authResult || q.openCorpId || q.clientCorpId) {
+ pollCertificationDetails(8)
+ }
})
onBeforeUnmount(() => {
window.removeEventListener('message', handleIframeMessage)
+ stopSignStatusPolling()
})
diff --git a/src/router/index.js b/src/router/index.js
index b3d60f8..911b5d4 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -320,6 +320,11 @@ const routes = [
component: () => import('@/pages/certification/IframeCallback.vue'),
meta: { requiresAuth: false },
},
+ {
+ path: '/certification/callback/:platform/:scene',
+ component: () => import('@/pages/certification/IframeCallback.vue'),
+ meta: { requiresAuth: false },
+ },
{
path: '/legal/:doc',
name: 'LegalReader',