5e7372d0c7
- 新增 app/components/HjcEnterpriseForm.vue:与 h5 EnterpriseForm 行为一致的共享表单,
字段集 / 必填规则 / OCR 回填 / 提示文案逐条对齐
- register.vue:一站式注册,一次性提交企业 + 经办人 + 授权 + 证件材料;
企业联系电话、企业地址选填,其余(含企业邮箱)必填,4 类证件必传
- tender/qualification.vue:改用共享组件,补「经办人身份证号」,
以真实文件上传 + OCR 取代原「证件 URL 文本框」;
审核中(0)/已通过(1) 只读,驳回(2)/未提交(-1) 可编辑重提
- 新增 server/api/tender/upload.post.ts(multipart 原样转发 → /hjc/auth/upload,匿名,
登录前后均可用)与 server/api/tender/ocr/recognize.post.ts(→ /hjc/ocr/recognize)
- 修正 datetime-local(YYYY-MM-DDTHH:mm)与后端 yyyy-MM-dd HH:mm:ss 的格式转换
- fix: server/api/tender/auth/{login,register}.post.ts 实际路由为 /api/tender/auth/*,
而客户端调用 /api/tender/{login,register},导致登录与注册此前均 404;
已按文件注释与同级接口约定移至扁平路径 server/api/tender/{login,register}.post.ts
- register / login / enterprise-save 三个代理改为原样透传 ApiResult{code,message,data},
useHjcAuth 与资质页按 code===0 判定,避免后端校验失败信息被吞掉
130 lines
4.3 KiB
Vue
130 lines
4.3 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 } = 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() {
|
||
const res: any = await $fetch('/api/tender/enterprise')
|
||
if (res) {
|
||
authStatus.value = res.authStatus ?? -1
|
||
rejectReason.value = res.rejectReason || ''
|
||
entForm.value?.fill(res)
|
||
}
|
||
}
|
||
|
||
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()
|
||
})
|
||
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>
|