Files
hjc-web/app/pages/tender/qualification.vue
T
weicw1996 d0c2dd17cb feat(tender): 登录加图形验证码、注册加短信验证码;改按 body.code 判定 401/403
- 新增 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)
- 下单成功但发起支付失败时停在支付步,避免用户重提交产生重复订单
2026-09-10 23:44:19 +08:00

148 lines
5.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>