Files
tyapi-frontend/src/pages/certification/index.vue

968 lines
26 KiB
Vue
Raw Normal View History

2025-11-24 16:06:44 +08:00
<template>
<div class="certification-page">
<!-- 加载状态 -->
<div v-if="loading" class="loading-container">
<div class="loading-content">
<div class="loading-spinner"></div>
<p class="loading-text">正在加载认证信息...</p>
</div>
</div>
<!-- 认证步骤条 -->
<div v-if="!loading && currentStep !== 'completed'" class="steps-container">
<div class="steps-header">
<h3 class="steps-title">企业入驻流程</h3>
<p class="steps-subtitle">完成企业入驻开启更多API服务功能</p>
</div>
<!-- 开发模式步骤控制器 -->
<div v-if="isDevelopment" class="dev-controller">
<div class="dev-controller-content">
<div class="dev-controller-label">
<el-icon class="dev-icon"><CodeBracketIcon /></el-icon>
<span>开发模式 - 步骤控制器</span>
</div>
<div class="dev-controller-controls">
<el-select
v-model="devCurrentStep"
placeholder="选择步骤"
size="small"
class="dev-step-select"
@change="handleDevStepChange"
>
<el-option
v-for="step in certificationSteps"
:key="step.key"
:label="step.title"
:value="step.key"
/>
</el-select>
<el-button
type="primary"
size="small"
@click="switchToStep(devCurrentStep)"
:disabled="!devCurrentStep"
class="dev-switch-btn"
>
切换到该步骤
</el-button>
<el-button type="info" size="small" @click="resetToFirstStep" class="dev-reset-btn">
重置到第一步
</el-button>
</div>
</div>
</div>
<CustomSteps
:steps="certificationSteps"
:current-step="currentStep"
class="certification-steps"
/>
</div>
<!-- 认证内容区域 -->
<div v-if="!loading" class="certification-content">
<EnterpriseInfo
v-if="currentStep === 'enterprise_info'"
:form-data="enterpriseForm"
2026-06-09 11:28:46 +08:00
:certification-status="certificationData?.status"
:failure-message="certificationData?.failure_message"
2025-11-24 16:06:44 +08:00
@submit="handleEnterpriseSubmit"
/>
2026-04-02 12:47:02 +08:00
<ManualReviewPending
2026-03-17 17:19:00 +08:00
v-if="currentStep === 'manual_review'"
:certification-data="certificationData"
:submit-time="manualReviewSubmitTime"
:company-name="enterpriseForm.companyName"
2026-07-26 23:14:54 +08:00
@refresh="() => getCertificationDetails({ silent: true })"
/>
<PlatformSelect
v-if="currentStep === 'platform_select'"
@selected="handlePlatformSelected"
2026-04-02 12:47:02 +08:00
/>
2026-03-17 17:19:00 +08:00
2025-11-24 16:06:44 +08:00
<EnterpriseVerify
v-if="currentStep === 'enterprise_verify'"
:enterprise-data="enterpriseForm"
:auth-url="stepMeta.auth_url"
@submit="handleEnterpriseVerifySubmit"
/>
<div v-if="currentStep === 'contract_sign'">
<ContractPreview
v-if="certificationData?.status === 'enterprise_verified'"
2026-07-26 23:14:54 +08:00
:contract-url="stepMeta.contract_url || stepMeta.ContractURL"
2025-11-24 16:06:44 +08:00
:loading="contractSignLoading"
@start-sign="handleStartContractSign"
/>
<ContractSign
v-else-if="certificationData?.status === 'contract_applied'"
:company-name="enterpriseForm.companyName"
:iframe-url="stepMeta.contract_sign_url"
/>
<ContractRejected
v-else-if="certificationData?.status === 'contract_rejected'"
:failure-message="certificationData?.failure_message"
@contact-service="handleContactService"
@go-back="handleGoBackFromRejected"
/>
<ContractExpired
v-else-if="certificationData?.status === 'contract_expired'"
:contract-created-at="
certificationData?.contract_info?.generated_at || certificationData?.contract_applied_at
"
@reapply-contract="handleReapplyContract"
@go-back="handleGoBackFromExpired"
/>
</div>
<CertificationComplete
v-if="currentStep === 'completed'"
:certification-data="certificationData"
@go-dashboard="goToDashboard"
@go-profile="goToProfile"
/>
</div>
</div>
</template>
<script setup>
import { certificationApi } from '@/api/index.js'
import CustomSteps from '@/components/common/CustomSteps.vue'
import { useUserStore } from '@/stores/user'
import {
BuildingOfficeIcon,
CheckCircleIcon,
2026-03-17 17:19:00 +08:00
ClockIcon,
2025-11-24 16:06:44 +08:00
CodeBracketIcon,
DocumentTextIcon,
UserIcon,
} from '@heroicons/vue/24/outline'
import { ElMessage } from 'element-plus'
import CertificationComplete from './components/CertificationComplete.vue'
import ContractExpired from './components/ContractExpired.vue'
import ContractPreview from './components/ContractPreview.vue'
import ContractRejected from './components/ContractRejected.vue'
import ContractSign from './components/ContractSign.vue'
import EnterpriseInfo from './components/EnterpriseInfo.vue'
import EnterpriseVerify from './components/EnterpriseVerify.vue'
2026-03-17 17:19:00 +08:00
import ManualReviewPending from './components/ManualReviewPending.vue'
2026-07-26 23:14:54 +08:00
import PlatformSelect from './components/PlatformSelect.vue'
2025-11-24 16:06:44 +08:00
const router = useRouter()
2026-07-26 23:14:54 +08:00
const route = useRoute()
2025-11-24 16:06:44 +08:00
const userStore = useUserStore()
const certificationSteps = [
{
key: 'enterprise_info',
title: '填写企业信息',
description: '填写企业基本信息和法人信息',
icon: BuildingOfficeIcon,
},
2026-04-02 12:47:02 +08:00
{
key: 'manual_review',
title: '人工审核',
description: '等待管理员审核企业信息',
icon: ClockIcon,
},
2026-07-26 23:14:54 +08:00
{
key: 'platform_select',
title: '选择平台',
description: '选择签署平台(法大大)',
icon: DocumentTextIcon,
},
2025-11-24 16:06:44 +08:00
{
key: 'enterprise_verify',
title: '企业认证',
description: '完成企业认证流程',
icon: UserIcon,
},
{
key: 'contract_sign',
title: '签署合同',
description: '签署认证合同',
icon: DocumentTextIcon,
},
{
key: 'completed',
title: '完成',
description: '认证流程完成',
icon: CheckCircleIcon,
},
]
// 认证状态数据
const certificationData = ref(null)
const loading = ref(false)
// 当前步骤状态
const currentStep = ref('enterprise_info')
const currentStepIndex = computed(() => {
return certificationSteps.findIndex((step) => step.key === currentStep.value)
})
// 步骤特定元数据
const stepMeta = ref({}) // 用于存储当前步骤的metadata
2026-03-17 17:19:00 +08:00
// 人工审核步骤的提交时间展示
const manualReviewSubmitTime = computed(() => {
const at = certificationData.value?.metadata?.enterprise_info?.submit_at ?? certificationData.value?.info_submitted_at
if (!at) return ''
try {
const d = new Date(at)
return Number.isNaN(d.getTime()) ? '' : d.toLocaleString('zh-CN')
} catch {
return ''
}
})
2025-11-24 16:06:44 +08:00
// 表单数据
const enterpriseForm = ref({
companyName: '',
unifiedSocialCode: '',
legalPersonName: '',
legalPersonID: '',
legalPersonPhone: '',
legalPersonCode: '',
enterpriseAddress: '',
enterpriseEmail: '',
})
2026-03-17 17:19:00 +08:00
//
2025-11-24 16:06:44 +08:00
const isDevelopment = ref(false)
const devCurrentStep = ref('enterprise_info')
// 合同签署加载状态
const contractSignLoading = ref(false)
2026-07-26 23:14:54 +08:00
// 合同签署状态轮询:进入 contract_applied 后定时拉详情,触发后端 checkAndUpdateSignStatus
let signPollTimer = null
const SIGN_POLL_INTERVAL = 5000
const signPolling = ref(false)
const autoSelectingPlatform = ref(false)
const stopSignStatusPolling = () => {
if (signPollTimer) {
clearTimeout(signPollTimer)
signPollTimer = null
}
signPolling.value = false
}
const startSignStatusPolling = () => {
if (signPollTimer) return
signPolling.value = true
const tick = async () => {
const status = certificationData.value?.status
if (status !== 'contract_applied') {
stopSignStatusPolling()
return
}
try {
const res = await certificationApi.getCertificationDetails()
if (res?.data) {
certificationData.value = res.data
stepMeta.value = res.data?.metadata || {}
const newStatus = res.data?.status
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)
}
// 仅当可见平台为 1 个时可静默 select否则展示 PlatformSelect
async function maybeGoPlatformSelectOrAuto() {
const meta = certificationData.value?.metadata || {}
if (!meta.need_select_platform) return false
const platforms = meta.available_platforms || []
if (platforms.length <= 1 && meta.auto_select_platform && !autoSelectingPlatform.value) {
autoSelectingPlatform.value = true
try {
const res = await certificationApi.selectSignPlatform({
sign_platform: meta.auto_select_platform,
})
if (res?.data) {
certificationData.value = res.data
stepMeta.value = res.data.metadata || {}
}
await getCertificationDetails({ silent: true })
return true
} catch (e) {
ElMessage.error(e?.message || '选择签署平台失败')
} finally {
autoSelectingPlatform.value = false
}
}
currentStep.value = 'platform_select'
return true
}
2026-03-20 13:08:22 +08:00
// 只补 enterprise_verify 所需 auth_url不改动原有页面展示数据结构
// 轮询策略:最多 5 秒,每秒 1 次(最多 5 次)
const pollAuthUrlOnly = async (maxTries = 5) => {
for (let i = 0; i < maxTries; i++) {
const res = await certificationApi.getCertificationDetails()
const status = res?.data?.status
const authUrl = res?.data?.metadata?.auth_url
// 仅同步状态,避免覆盖已有展示字段
if (status && certificationData.value) {
certificationData.value.status = status
await setCurrentStepByStatus()
}
if (authUrl) {
// 保留原 metadata仅更新链接字段
stepMeta.value = {
...(stepMeta.value || {}),
auth_url: authUrl,
}
return authUrl
}
await new Promise((resolve) => setTimeout(resolve, 1000))
}
return ''
}
2026-03-17 17:19:00 +08:00
// 事件处理:优先用提交接口返回的认证数据更新步骤,确保进入「人工审核」页,避免依赖二次请求
const handleEnterpriseSubmit = async (payload) => {
2025-11-24 16:06:44 +08:00
try {
loading.value = true
2026-03-17 17:19:00 +08:00
const nextAction = payload?.response?.data?.metadata?.next_action
if (nextAction) {
ElMessage.success(nextAction)
} else {
ElMessage.success('企业信息提交成功,请等待管理员审核')
}
if (payload?.response?.data?.status) {
certificationData.value = payload.response.data
stepMeta.value = payload.response.data?.metadata || {}
await setCurrentStepByStatus()
2026-03-20 13:08:22 +08:00
if (currentStep.value === 'enterprise_verify' && !stepMeta.value?.auth_url) {
await pollAuthUrlOnly()
}
2026-03-17 17:19:00 +08:00
} else {
await getCertificationDetails()
2026-03-20 13:08:22 +08:00
if (currentStep.value === 'enterprise_verify' && !stepMeta.value?.auth_url) {
await pollAuthUrlOnly()
}
2025-11-24 16:06:44 +08:00
}
} catch (error) {
2026-03-17 17:19:00 +08:00
ElMessage.error(error?.message || '获取认证状态失败,请刷新页面')
2025-11-24 16:06:44 +08:00
} finally {
loading.value = false
}
}
2026-07-26 23:14:54 +08:00
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()
}
}
2025-11-24 16:06:44 +08:00
const handleEnterpriseVerifySubmit = async () => {
try {
ElMessage.success('企业认证成功')
currentStep.value = 'contract_sign'
} catch {
ElMessage.error('企业认证失败,请重试')
}
}
const handleStartContractSign = async () => {
try {
contractSignLoading.value = true
2026-07-26 23:14:54 +08:00
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 || '',
}
}
2025-11-24 16:06:44 +08:00
ElMessage.success('合同签署流程已启动')
await getCertificationDetails()
2026-07-26 23:14:54 +08:00
if (embedUrl) {
stepMeta.value = {
...(stepMeta.value || {}),
contract_sign_url: embedUrl,
}
}
2025-11-24 16:06:44 +08:00
} catch (error) {
ElMessage.error(error?.message || '发起签署失败,请稍后重试')
} finally {
contractSignLoading.value = false
}
}
const handleContactService = () => {
// 这里可以打开客服聊天窗口或跳转到客服页面
ElMessage.success('正在为您连接客服...')
// 可以添加具体的客服逻辑,比如打开聊天窗口
}
const handleGoBackFromRejected = () => {
// 从拒签状态返回,重置到企业认证状态
currentStep.value = 'enterprise_verify'
}
const handleGoBackFromExpired = () => {
// 从过期状态返回,重置到企业认证状态
currentStep.value = 'enterprise_verify'
}
const handleReapplyContract = async () => {
try {
contractSignLoading.value = true
// 重新申请合同签署
await certificationApi.applyContract()
ElMessage.success('合同重新申请成功')
await getCertificationDetails()
} catch (error) {
ElMessage.error(error?.message || '重新申请失败,请稍后重试')
} finally {
contractSignLoading.value = false
}
}
const handleDevStepChange = (value) => {
devCurrentStep.value = value
}
const switchToStep = (stepKey) => {
currentStep.value = stepKey
}
const resetToFirstStep = () => {
currentStep.value = 'enterprise_info'
}
// 跳转到控制台
const goToDashboard = () => {
router.push('/products')
}
// 跳转到账户中心
const goToProfile = () => {
router.push('/profile')
}
2026-07-26 23:14:54 +08:00
// 获取认证详情silent=true 时不展示全页 loading用于人工审核/平台轮询)
const getCertificationDetails = async (opts = {}) => {
const silent = opts?.silent === true
2025-11-24 16:06:44 +08:00
try {
2026-07-26 23:14:54 +08:00
if (!silent) loading.value = true
2025-11-24 16:06:44 +08:00
const res = await certificationApi.getCertificationDetails()
certificationData.value = res.data
console.log('s', res)
// 回显企业信息 - 修复字段路径问题
if (res.data && res.data.metadata?.enterprise_info) {
const enterpriseInfo = res.data.metadata.enterprise_info
// 只有当后端有数据时才覆盖,避免清空用户刚填写的信息
if (enterpriseInfo.company_name) {
enterpriseForm.value.companyName = enterpriseInfo.company_name
}
if (enterpriseInfo.unified_social_code) {
enterpriseForm.value.unifiedSocialCode = enterpriseInfo.unified_social_code
}
if (enterpriseInfo.legal_person_name) {
enterpriseForm.value.legalPersonName = enterpriseInfo.legal_person_name
}
if (enterpriseInfo.legal_person_id) {
enterpriseForm.value.legalPersonID = enterpriseInfo.legal_person_id
}
if (enterpriseInfo.legal_person_phone) {
enterpriseForm.value.legalPersonPhone = enterpriseInfo.legal_person_phone
}
if (enterpriseInfo.enterprise_address) {
enterpriseForm.value.enterpriseAddress = enterpriseInfo.enterprise_address
}
if (enterpriseInfo.enterprise_email) {
enterpriseForm.value.enterpriseEmail = enterpriseInfo.enterprise_email
}
}
// 步骤特定元数据
stepMeta.value = res.data?.metadata || {}
await setCurrentStepByStatus()
2026-07-26 23:14:54 +08:00
return true
2025-11-24 16:06:44 +08:00
} catch (error) {
console.error('获取认证详情失败:', error)
ElMessage.error('获取认证详情失败')
2026-07-26 23:14:54 +08:00
if (certificationData.value) {
await setCurrentStepByStatus()
}
return false
2025-11-24 16:06:44 +08:00
} finally {
loading.value = false
}
}
// 根据认证状态设置当前步骤
const setCurrentStepByStatus = async () => {
if (!certificationData.value) {
currentStep.value = 'enterprise_info'
return
}
const previousStatus = currentStep.value
const newStatus = certificationData.value.status
switch (newStatus) {
case 'pending':
currentStep.value = 'enterprise_info'
break
2026-03-17 17:19:00 +08:00
case 'info_pending_review':
2026-07-26 23:14:54 +08:00
if (certificationData.value?.metadata?.need_select_platform) {
if (await maybeGoPlatformSelectOrAuto()) return
} else {
currentStep.value = 'manual_review'
}
2026-03-17 17:19:00 +08:00
break
2025-11-24 16:06:44 +08:00
case 'info_submitted':
2026-07-26 23:14:54 +08:00
if (
!certificationData.value?.metadata?.auth_url &&
certificationData.value?.metadata?.need_select_platform
) {
if (await maybeGoPlatformSelectOrAuto()) return
} else {
currentStep.value = 'enterprise_verify'
}
2025-11-24 16:06:44 +08:00
break
case 'enterprise_verified':
2026-07-26 23:14:54 +08:00
if (
!certificationData.value?.metadata?.esign_flow_id &&
!certificationData.value?.metadata?.sign_task_ready
) {
if (await maybeGoPlatformSelectOrAuto()) return
currentStep.value = 'platform_select'
} else {
currentStep.value = 'contract_sign'
}
break
2025-11-24 16:06:44 +08:00
case 'contract_applied':
2026-07-26 23:14:54 +08:00
currentStep.value = 'contract_sign'
startSignStatusPolling()
break
2025-11-24 16:06:44 +08:00
case 'contract_rejected':
case 'contract_expired':
currentStep.value = 'contract_sign'
2026-07-26 23:14:54 +08:00
stopSignStatusPolling()
2025-11-24 16:06:44 +08:00
break
case 'contract_signed':
case 'completed':
currentStep.value = 'completed'
2026-07-26 23:14:54 +08:00
stopSignStatusPolling()
2025-11-24 16:06:44 +08:00
// 认证完成时重新获取用户信息
if (previousStatus !== 'completed') {
try {
await userStore.fetchUserProfile()
console.log('认证完成,已重新获取用户信息')
} catch (error) {
console.error('重新获取用户信息失败:', error)
}
}
break
case 'info_rejected':
currentStep.value = 'enterprise_info'
break
default:
currentStep.value = 'enterprise_info'
}
}
// 认证详情轮询用于iframe回调后确认状态变化
async function pollCertificationDetails(maxTries = 5) {
let tries = 0
let lastStatus = certificationData.value?.status
let changed = false
while (tries < maxTries && !changed) {
try {
// 不显示loading
const res = await certificationApi.getCertificationDetails()
const newStatus = res.data?.status
if (newStatus !== lastStatus) {
certificationData.value = res.data
// 回显企业信息 - 修复字段路径问题
if (res.data && res.data.metadata?.enterprise_info) {
const enterpriseInfo = res.data.metadata.enterprise_info
// 只有当后端有数据时才覆盖,避免清空用户刚填写的信息
if (enterpriseInfo.company_name) {
enterpriseForm.value.companyName = enterpriseInfo.company_name
}
if (enterpriseInfo.unified_social_code) {
enterpriseForm.value.unifiedSocialCode = enterpriseInfo.unified_social_code
}
if (enterpriseInfo.legal_person_name) {
enterpriseForm.value.legalPersonName = enterpriseInfo.legal_person_name
}
if (enterpriseInfo.legal_person_id) {
enterpriseForm.value.legalPersonID = enterpriseInfo.legal_person_id
}
if (enterpriseInfo.legal_person_phone) {
enterpriseForm.value.legalPersonPhone = enterpriseInfo.legal_person_phone
}
}
stepMeta.value = res.data?.metadata || {}
await setCurrentStepByStatus()
changed = true
break
}
} catch (error) {
// 可选:打印错误
console.warn('轮询认证详情失败:', error)
}
tries++
const delay = Math.min(tries, 5) * 1000 // 1s,2s,3s,4s,5s
await new Promise((resolve) => setTimeout(resolve, delay))
}
}
// Iframe 回调消息监听
const handledScenes = new Set()
async function handleIframeMessage(event) {
// 这里可以加安全校验event.origin === '你的主站点origin'
// 目前只打印消息,后续可补充具体逻辑
const { type, scene } = event.data || {}
if (type === 'CHECK_STATUS') {
if (handledScenes.has(scene)) {
console.log('该scene已处理忽略重复消息:', scene)
return
}
console.log('收到iframe消息:', event)
handledScenes.add(scene)
console.log('需要检查后端状态scene:', scene)
try {
if (scene === 'sign') {
// 合同签署场景,调用 confirmSign 接口
const response = await certificationApi.confirmSign()
console.log('confirmSign 接口返回:', response)
// 确认签署状态后,重新获取完整的认证详情
await getCertificationDetails()
} else {
// 企业认证场景,使用原有的轮询方式
pollCertificationDetails()
}
} catch (error) {
console.error('检查状态失败:', error)
ElMessage.error('检查状态失败,请稍后重试')
}
}
}
// 初始化
onMounted(() => {
2026-07-26 23:14:54 +08:00
// 若法大大等第三方在 iframe 内回跳到本页,立即顶层打开入驻页
if (window.top && window.top !== window.self) {
const target = `${window.location.origin}/profile/certification${window.location.search || ''}`
window.top.location.href = target
return
}
2025-11-24 16:06:44 +08:00
window.addEventListener('message', handleIframeMessage)
2026-07-26 23:14:54 +08:00
getCertificationDetails().then(async () => {
if (route.query?.signCallback) {
try {
await certificationApi.confirmSign()
await getCertificationDetails()
} catch (e) {
console.warn('签署回调确认失败,将轮询状态', e)
pollCertificationDetails(8)
}
}
})
const q = route.query || {}
if (q.authResult || q.openCorpId || q.clientCorpId) {
pollCertificationDetails(8)
}
2025-11-24 16:06:44 +08:00
})
onBeforeUnmount(() => {
window.removeEventListener('message', handleIframeMessage)
2026-07-26 23:14:54 +08:00
stopSignStatusPolling()
2025-11-24 16:06:44 +08:00
})
</script>
<style scoped>
.certification-page {
min-height: 100vh;
position: relative;
}
/* 加载状态 */
.loading-container {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1);
padding: 60px;
margin: 40px auto;
text-align: center;
min-height: 600px;
display: flex;
align-items: center;
justify-content: center;
}
.loading-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 24px;
}
.loading-spinner {
width: 48px;
height: 48px;
border: 4px solid #e2e8f0;
border-top: 4px solid #3b82f6;
border-radius: 50%;
animation: spin 1s linear infinite;
}
.loading-text {
font-size: 16px;
color: #64748b;
font-weight: 500;
}
/* 步骤条容器 */
.steps-container {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1);
padding: 36px;
margin: 0 auto 32px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.steps-header {
text-align: center;
margin-bottom: 24px;
}
.steps-title {
font-size: 28px;
font-weight: 700;
color: #1e293b;
margin-bottom: 6px;
background: linear-gradient(135deg, #1e293b 0%, #475569 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.steps-subtitle {
font-size: 16px;
color: #64748b;
font-weight: 500;
}
.certification-steps {
margin-bottom: 0;
}
.certification-content {
min-height: 600px;
margin: 0 auto;
}
/* 开发模式控制器样式 */
.dev-controller {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1);
padding: 24px;
margin-bottom: 48px;
border: 1px solid rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
gap: 20px;
}
.dev-controller-content {
display: flex;
align-items: center;
gap: 20px;
}
.dev-controller-label {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
font-weight: 600;
color: #1e293b;
}
.dev-controller-controls {
display: flex;
align-items: center;
gap: 10px;
}
.dev-step-select :deep(.el-input__inner) {
border-radius: 8px;
border-color: #e2e8f0;
background-color: #f8fafc;
}
.dev-step-select :deep(.el-input__inner:focus) {
border-color: #3b82f6;
box-shadow: 0 0 0 2px #3b82f6;
}
.dev-switch-btn :deep(.el-button--small) {
border-radius: 8px;
background-color: #3b82f6;
border-color: #3b82f6;
}
.dev-switch-btn :deep(.el-button--small:hover) {
background-color: #1d4ed8;
border-color: #1d4ed8;
}
.dev-reset-btn :deep(.el-button--small) {
border-radius: 8px;
background-color: #e2e8f0;
border-color: #e2e8f0;
color: #64748b;
}
.dev-reset-btn :deep(.el-button--small:hover) {
background-color: #cbd5e1;
border-color: #cbd5e1;
color: #475569;
}
.dev-icon {
width: 20px;
height: 20px;
color: #3b82f6;
}
/* 动画效果 */
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
/* 响应式设计 */
@media (max-width: 768px) {
.certification-page {
padding: 20px 16px;
}
.steps-container {
padding: 32px 24px;
margin-bottom: 24px;
}
.steps-title {
font-size: 24px;
}
.steps-subtitle {
font-size: 14px;
}
.certification-steps :deep(.el-step__icon) {
width: 40px;
height: 40px;
}
.certification-steps :deep(.el-step__title) {
font-size: 14px;
}
.certification-steps :deep(.el-step__description) {
font-size: 12px;
}
.loading-container {
margin: 20px 16px;
padding: 40px 24px;
}
.dev-controller {
flex-direction: column;
align-items: flex-start;
gap: 15px;
padding: 16px;
}
.dev-controller-content {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.dev-controller-label {
font-size: 14px;
}
.dev-controller-controls {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.dev-step-select :deep(.el-input__inner) {
width: 100%;
}
.dev-switch-btn,
.dev-reset-btn {
width: 100%;
}
}
@media (max-width: 480px) {
.certification-steps :deep(.el-step__icon) {
width: 36px;
height: 36px;
}
.certification-steps :deep(.el-step__icon.is-text) {
font-size: 16px;
}
}
</style>