d0c2dd17cb
- 新增 captcha / sms / logout 三个 Nuxt server 代理 - 修复代理把 ApiResult 拍扁成 data 的问题(my-orders、enterprise、order、pay、 mark-paid):登录态失败被前端当成「没有订单 / 未提交资质」,无法区分 - 新增 handleAuthCode 统一处理:401 → 清凭据并跳 /login?redirect=…; 403 → 只提示「没有访问权限」,不清 token、不跳登录页 - 登录页与注册页分别接入图形验证码与短信验证码(含 60s 倒计时) - 修复回跳开放重定向:redirect 参数安全解码且只接受站内路径 (拒绝 //evil.com、https://… 及反斜杠变体 /\evil.com) - 下单成功但发起支付失败时停在支付步,避免用户重提交产生重复订单
148 lines
5.0 KiB
Vue
148 lines
5.0 KiB
Vue
<template>
|
||
<div class="container mx-auto px-4 py-10">
|
||
<div class="mb-6 flex items-center justify-between">
|
||
<h1 class="text-2xl font-bold text-gray-900">企业资质</h1>
|
||
<HjcUserBar />
|
||
</div>
|
||
|
||
<div v-if="!loggedIn" class="py-20 text-center">
|
||
<p class="text-gray-500">请先登录后管理企业资质</p>
|
||
<NuxtLink
|
||
to="/login?redirect=/tender/qualification"
|
||
class="mt-4 inline-block rounded-md bg-blue-600 px-6 py-2 text-white hover:bg-blue-700"
|
||
>
|
||
去登录
|
||
</NuxtLink>
|
||
</div>
|
||
|
||
<div v-else class="mx-auto max-w-2xl rounded-lg border border-gray-200 p-6">
|
||
<!-- 审核状态 -->
|
||
<div class="mb-6 rounded-md bg-gray-50 p-4 text-sm">
|
||
<span class="text-gray-600">审核状态:</span>
|
||
<span :class="authStatusClass" class="rounded px-2 py-0.5 font-medium">{{ authStatusText }}</span>
|
||
<p v-if="authStatus === 2 && rejectReason" class="mt-2 text-red-500">驳回原因:{{ rejectReason }}</p>
|
||
<p class="mt-1 text-xs text-gray-400">资质审核通过后方可购买标书</p>
|
||
</div>
|
||
|
||
<form class="space-y-4" @submit.prevent="submit">
|
||
<HjcEnterpriseForm ref="entForm" v-model:form="form" v-model:materials="materials" :readonly="readonly" />
|
||
|
||
<p v-if="readonly" class="text-center text-xs text-gray-500">
|
||
资质审核中 / 已通过时不可修改;被驳回后可修改并重新提交。
|
||
</p>
|
||
|
||
<button
|
||
v-if="!readonly"
|
||
type="submit"
|
||
class="w-full rounded-md bg-blue-600 py-3 font-medium text-white hover:bg-blue-700 disabled:opacity-50"
|
||
:disabled="submitting"
|
||
>
|
||
{{ submitting ? '提交中...' : '提交审核' }}
|
||
</button>
|
||
<p v-if="error" class="text-center text-sm text-red-500">{{ error }}</p>
|
||
<p v-if="tip" class="text-center text-sm text-green-600">{{ tip }}</p>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
const { isLoggedIn, handleAuthCode } = useHjcAuth()
|
||
const loggedIn = ref(false)
|
||
const authStatus = ref(-1)
|
||
const rejectReason = ref('')
|
||
const submitting = ref(false)
|
||
const error = ref('')
|
||
const tip = ref('')
|
||
const entForm = ref<any>(null)
|
||
|
||
const form = ref<Record<string, any>>({
|
||
name: '',
|
||
creditCode: '',
|
||
contactPhone: '',
|
||
contactEmail: '',
|
||
address: '',
|
||
agentName: '',
|
||
idCardNo: '',
|
||
agentEmail: '',
|
||
agentPhone: '',
|
||
authorizeExpire: ''
|
||
})
|
||
|
||
const materials = ref<any[]>([
|
||
{ materialType: 'idcard_front', label: '身份证人像面', materialName: '身份证人像面', fileUrl: '' },
|
||
{ materialType: 'idcard_back', label: '身份证国徽面', materialName: '身份证国徽面', fileUrl: '' },
|
||
{ materialType: 'handbook', label: '授权委托书', materialName: '授权委托书', fileUrl: '' },
|
||
{ materialType: 'license', label: '营业执照', materialName: '营业执照', fileUrl: '' }
|
||
])
|
||
|
||
const authStatusText = computed(
|
||
() => ({ 0: '待审核', 1: '已通过', 2: '已驳回' } as Record<number, string>)[authStatus.value] ?? '未提交资质'
|
||
)
|
||
const authStatusClass = computed(() => {
|
||
if (authStatus.value === 1) return 'bg-green-100 text-green-700'
|
||
if (authStatus.value === 2) return 'bg-red-100 text-red-600'
|
||
return 'bg-amber-100 text-amber-700'
|
||
})
|
||
/** 审核中(0)/已通过(1) 只读;驳回(2)/未提交(-1) 可编辑重提(与 h5 一致) */
|
||
const readonly = computed(() => authStatus.value === 0 || authStatus.value === 1)
|
||
|
||
async function load() {
|
||
// 代理原样透传 ApiResult:按 body.code 判定(HTTP 状态码恒为 200,不可用于判定)
|
||
const res: any = await $fetch('/api/tender/enterprise')
|
||
const auth = await handleAuthCode(res, '/tender/qualification')
|
||
if (auth.handled) {
|
||
// 401 已清凭据并跳登录页;403 只提示「没有访问权限」,不清 token
|
||
error.value = auth.message || ''
|
||
loggedIn.value = isLoggedIn()
|
||
return
|
||
}
|
||
if (res?.code !== 0) {
|
||
error.value = res?.message || '资质信息加载失败'
|
||
return
|
||
}
|
||
const data = res.data
|
||
if (data) {
|
||
authStatus.value = data.authStatus ?? -1
|
||
rejectReason.value = data.rejectReason || ''
|
||
entForm.value?.fill(data)
|
||
}
|
||
}
|
||
|
||
async function submit() {
|
||
error.value = ''
|
||
tip.value = ''
|
||
if (!entForm.value?.validate()) return
|
||
submitting.value = true
|
||
try {
|
||
const res: any = await $fetch('/api/tender/enterprise/save', {
|
||
method: 'POST',
|
||
body: entForm.value.buildPayload()
|
||
})
|
||
const auth = await handleAuthCode(res, '/tender/qualification')
|
||
if (auth.handled) {
|
||
error.value = auth.message || ''
|
||
return
|
||
}
|
||
if (res?.code !== 0) {
|
||
error.value = res?.message || '提交失败'
|
||
return
|
||
}
|
||
tip.value = res?.message || '提交成功,等待平台审核'
|
||
await load()
|
||
} catch (e: any) {
|
||
error.value = e?.data?.statusMessage || e?.data?.message || e?.message || '提交失败'
|
||
} finally {
|
||
submitting.value = false
|
||
}
|
||
}
|
||
|
||
loggedIn.value = isLoggedIn()
|
||
onMounted(async () => {
|
||
loggedIn.value = isLoggedIn()
|
||
if (loggedIn.value) {
|
||
await load()
|
||
}
|
||
})
|
||
</script>
|