feat(tender): 阶段3b 企业登录/注册 + 购买→支付→确认 闭环
- useHjcAuth:token 存 cookie(hjc_token),login/register/isLoggedIn
- 页面 /login /register(企业名称+密码)
- server/api/tender/{auth/login,auth/register,pay,mark-paid} 代理;order 代理从 cookie 读取 hjc_token 透传 Authorization
- BuyDocument:未登录跳登录;下单→pay(codeUrl)→确认 mark-paid→成功
- types 增 HjcUser
This commit is contained in:
@@ -2,7 +2,17 @@
|
||||
<div class="container mx-auto px-4 py-10">
|
||||
<NuxtLink to="/tender" class="text-sm text-blue-600 hover:underline">← 返回标书中心</NuxtLink>
|
||||
|
||||
<div v-if="tender" class="mx-auto mt-6 max-w-2xl rounded-lg border border-gray-200 p-6">
|
||||
<!-- 未登录提示 -->
|
||||
<div v-if="!loggedIn" class="mx-auto mt-6 max-w-xl rounded-lg border border-gray-200 p-8 text-center">
|
||||
<p class="text-lg text-gray-700">购买标书需要先登录企业账号</p>
|
||||
<div class="mt-6 flex gap-4 justify-center">
|
||||
<NuxtLink :to="`/login?redirect=${redirect}`" class="rounded-md bg-blue-600 px-6 py-2 text-white hover:bg-blue-700">去登录</NuxtLink>
|
||||
<NuxtLink :to="`/register?redirect=${redirect}`" class="rounded-md border border-gray-300 px-6 py-2 text-gray-700 hover:bg-gray-50">企业注册</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表单 -->
|
||||
<div v-else-if="tender && step === 'form'" class="mx-auto mt-6 max-w-2xl rounded-lg border border-gray-200 p-6">
|
||||
<h1 class="text-xl font-bold text-gray-900">购买标书</h1>
|
||||
<div class="mt-4 rounded-md bg-gray-50 p-4 text-sm">
|
||||
<p class="font-semibold text-gray-800">{{ tender.projectName }}</p>
|
||||
@@ -30,19 +40,34 @@
|
||||
<input v-model.number="form.quantity" type="number" min="1" class="mt-1 w-full rounded-md border border-gray-300 px-4 py-2 text-sm" />
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full rounded-md bg-blue-600 py-3 text-white font-medium hover:bg-blue-700 disabled:opacity-50"
|
||||
:disabled="submitting"
|
||||
>
|
||||
{{ submitting ? '提交中...' : '提交订单并支付' }}
|
||||
<button type="submit" class="w-full rounded-md bg-blue-600 py-3 text-white font-medium hover:bg-blue-700 disabled:opacity-50" :disabled="submitting">
|
||||
{{ submitting ? '提交中...' : '提交订单并用微信支付' }}
|
||||
</button>
|
||||
|
||||
<p v-if="tip" class="text-center text-sm text-gray-500">{{ tip }}</p>
|
||||
<p v-if="error" class="text-center text-sm text-red-500">{{ error }}</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 支付 -->
|
||||
<div v-else-if="step === 'pay'" class="mx-auto mt-6 max-w-2xl rounded-lg border border-gray-200 p-8 text-center">
|
||||
<h1 class="text-xl font-bold text-gray-900">请使用微信扫码支付</h1>
|
||||
<p class="mt-2 text-sm text-gray-500">订单号:{{ orderNo }}</p>
|
||||
<p class="my-6 text-2xl font-bold text-blue-600">¥{{ formatMoney(totalAmount) }}</p>
|
||||
<div class="rounded-md bg-gray-50 p-4 text-sm break-all text-gray-700">
|
||||
扫码链接:<a :href="codeUrl" target="_blank" class="text-blue-600 hover:underline">{{ codeUrl }}</a>
|
||||
</div>
|
||||
<button class="mt-6 w-full rounded-md bg-blue-600 py-3 text-white font-medium hover:bg-blue-700" :disabled="marking" @click="markPaid">
|
||||
{{ marking ? '确认中...' : '支付已完成,确认订单' }}
|
||||
</button>
|
||||
<p v-if="error" class="mt-3 text-sm text-red-500">{{ error }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 完成 -->
|
||||
<div v-else-if="step === 'done'" class="mx-auto mt-6 max-w-2xl rounded-lg border border-green-200 bg-green-50 p-8 text-center">
|
||||
<p class="text-xl font-bold text-green-700">购买成功</p>
|
||||
<p class="mt-2 text-sm text-green-600">订单号:{{ orderNo }},标书已可查看/下载</p>
|
||||
<NuxtLink to="/tender" class="mt-6 inline-block rounded-md bg-green-600 px-6 py-2 text-white hover:bg-green-700">返回标书中心</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div v-else-if="notFound" class="py-20 text-center text-gray-500">项目不存在</div>
|
||||
<SiteLoading v-else />
|
||||
</div>
|
||||
@@ -53,18 +78,21 @@ import type { Tender } from '~/types/tender'
|
||||
|
||||
const route = useRoute()
|
||||
const id = route.query.id as string
|
||||
const { isLoggedIn } = useHjcAuth()
|
||||
|
||||
const loggedIn = ref(false)
|
||||
const tender = shallowRef<Tender | null>(null)
|
||||
const notFound = ref(false)
|
||||
const submitting = ref(false)
|
||||
const tip = ref('')
|
||||
const marking = ref(false)
|
||||
const error = ref('')
|
||||
const step = ref<'form' | 'pay' | 'done'>('form')
|
||||
const orderNo = ref('')
|
||||
const codeUrl = ref('')
|
||||
const totalAmount = ref(0)
|
||||
|
||||
const form = reactive({
|
||||
contactName: '',
|
||||
contactPhone: '',
|
||||
contactEmail: '',
|
||||
quantity: 1
|
||||
})
|
||||
const form = reactive({ contactName: '', contactPhone: '', contactEmail: '', quantity: 1 })
|
||||
const redirect = computed(() => `/buy?id=${id}`)
|
||||
|
||||
function formatMoney(n?: number | string) {
|
||||
return Number(n ?? 0).toFixed(2)
|
||||
@@ -72,10 +100,9 @@ function formatMoney(n?: number | string) {
|
||||
|
||||
async function submit() {
|
||||
error.value = ''
|
||||
tip.value = ''
|
||||
submitting.value = true
|
||||
try {
|
||||
const res: any = await $fetch('/api/tender/order', {
|
||||
const order: any = await $fetch('/api/tender/order', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
projectId: Number(id),
|
||||
@@ -85,11 +112,15 @@ async function submit() {
|
||||
contactEmail: form.contactEmail
|
||||
}
|
||||
})
|
||||
if (res && res.orderNo) {
|
||||
tip.value = `下单成功,订单号:${res.orderNo},请扫码支付`
|
||||
// TODO(阶段3b): 调 /api/tender/pay 获取微信二维码(需登录态)
|
||||
if (order && order.orderNo) {
|
||||
orderNo.value = order.orderNo
|
||||
totalAmount.value = order.totalAmount || 0
|
||||
// 发起支付
|
||||
const pay: any = await $fetch('/api/tender/pay', { method: 'POST', body: { orderNo: order.orderNo } })
|
||||
codeUrl.value = pay?.codeUrl || ''
|
||||
step.value = 'pay'
|
||||
} else {
|
||||
error.value = res?.message || '下单失败'
|
||||
error.value = order?.message || '下单失败'
|
||||
}
|
||||
} catch (e: any) {
|
||||
error.value = e?.data?.message || e?.message || '下单失败'
|
||||
@@ -98,6 +129,21 @@ async function submit() {
|
||||
}
|
||||
}
|
||||
|
||||
async function markPaid() {
|
||||
error.value = ''
|
||||
marking.value = true
|
||||
try {
|
||||
await $fetch('/api/tender/mark-paid', { method: 'PUT', body: { orderNo: orderNo.value } })
|
||||
step.value = 'done'
|
||||
} catch (e: any) {
|
||||
error.value = e?.data?.message || e?.message || '确认失败'
|
||||
} finally {
|
||||
marking.value = false
|
||||
}
|
||||
}
|
||||
|
||||
loggedIn.value = isLoggedIn()
|
||||
|
||||
try {
|
||||
if (!id) {
|
||||
notFound.value = true
|
||||
|
||||
Reference in New Issue
Block a user