feat(tender): 我的订单加「去支付」「取消订单」入口,并新建收银台

hjc-web 此前**没有收银台**——支付步骤内嵌在购标流程(BuyDocument 的 step='pay')里,
买家一旦离开就再也回不到未完成的支付。本提交补上按订单号付款的独立页面。

- 新建 /tender/pay 收银台:只认订单号,金额取自**订单本身**(by-no)而非支付响应
  (支付一失败页面就会停在 0.00);进入先判状态,已支付/已取消/已退款**不发**统一下单,
  就地显示结论——既无意义,也会多撞一次跨端 OUT_TRADE_NO_USED
- 订单列表:待支付卡片加两个按钮 + 手搓 Tailwind 二次确认弹窗(照 ConsultDialog 的风格,
  不用 antd 的 Modal:买家页无先例,视觉与站点自建风格不一致)
- 状态口径改读 orderStatus(原先只看 payStatus,取消后会把已取消显示成「待支付」
  并带上「去支付」),抽 app/utils/orderStatus.ts,与 hjc-h5 刻意保持同一套优先级
- 新增两个 Nitro 代理 order-by-no.get.ts / cancel.post.ts,照既有 my-orders / pay-status
  的范式原样透传 ApiResult
- 标书详情「购买人数」改读 buyerCount(后端实时统计的已付款订单数)
- 跨端 OUT_TRADE_NO_USED:识别后端回带的 data.wechatCode 并给可行动提示,
  不匹配 message 字符串(包装方式一改就会静默失效)
This commit is contained in:
2026-09-17 03:39:59 +08:00
parent 81ef9317c7
commit a7399e0624
8 changed files with 681 additions and 34 deletions
+128
View File
@@ -0,0 +1,128 @@
<template>
<ClientOnly>
<Teleport to="body">
<Transition name="hjc-confirm-fade">
<div
v-if="open"
class="fixed inset-0 z-[110] flex items-center justify-center p-4"
@click.self="onCancel"
>
<!-- 遮罩 -->
<div class="absolute inset-0 bg-black/50" />
<!-- 弹窗卡片 ConsultDialog.vue 的手搓 Tailwind 风格不用 antd Modal
ant-design-vue 虽已装且自动导入但买家页没有先例视觉与站点自建风格不一致 -->
<Transition name="hjc-confirm-pop" appear>
<div
v-if="open"
class="relative w-full max-w-sm rounded-2xl bg-white p-6 shadow-2xl"
role="dialog"
aria-modal="true"
:aria-label="title"
>
<h2 class="text-lg font-bold text-gray-900">{{ title }}</h2>
<p v-if="content" class="mt-2 text-sm leading-relaxed text-gray-600">{{ content }}</p>
<div class="mt-6 flex justify-end gap-3">
<button
type="button"
class="rounded-md border border-gray-300 px-4 py-2 text-sm text-gray-700 hover:bg-gray-50 disabled:opacity-50"
:disabled="loading"
@click="onCancel"
>
{{ cancelText }}
</button>
<button
type="button"
class="rounded-md bg-blue-600 px-4 py-2 text-sm text-white hover:bg-blue-700 disabled:opacity-50"
:disabled="loading"
@click="emit('confirm')"
>
{{ loading ? loadingText : confirmText }}
</button>
</div>
</div>
</Transition>
</div>
</Transition>
</Teleport>
</ClientOnly>
</template>
<script setup lang="ts">
/**
* 通用二次确认弹窗(破坏性操作用)。
*
* 与 ConsultDialog.vue 同款实现路径:`ClientOnly` + `Teleport to="body"` + 手搓 Tailwind。
* 组件本身不持有任何业务状态——开合由父组件通过 `open` 控制,结果通过 `confirm`/`cancel` 事件回传,
* 这样"点遮罩不发请求"这类行为天然成立(父组件只在 confirm 里发请求)。
*/
const props = withDefaults(defineProps<{
open: boolean
title: string
content?: string
confirmText?: string
cancelText?: string
loadingText?: string
/** 确认动作进行中:两个按钮都禁用,防连点 */
loading?: boolean
}>(), {
content: '',
confirmText: '确定',
cancelText: '再想想',
loadingText: '处理中...',
loading: false
})
const emit = defineEmits<{ confirm: []; cancel: [] }>()
function onCancel() {
if (props.loading) return
emit('cancel')
}
/** ESC 关闭;打开时锁背景滚动,关闭时复位 */
function onKeydown(e: KeyboardEvent) {
if (e.key === 'Escape' && props.open) onCancel()
}
watch(() => props.open, (isOpen) => {
if (!import.meta.client) return
document.body.style.overflow = isOpen ? 'hidden' : ''
})
onMounted(() => {
window.addEventListener('keydown', onKeydown)
})
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKeydown)
if (import.meta.client) document.body.style.overflow = ''
})
</script>
<style scoped>
.hjc-confirm-fade-enter-active,
.hjc-confirm-fade-leave-active {
transition: opacity 0.2s ease;
}
.hjc-confirm-fade-enter-from,
.hjc-confirm-fade-leave-to {
opacity: 0;
}
.hjc-confirm-pop-enter-active {
transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.25s ease;
}
.hjc-confirm-pop-leave-active {
transition: transform 0.2s ease, opacity 0.2s ease;
}
.hjc-confirm-pop-enter-from {
transform: scale(0.95) translateY(10px);
opacity: 0;
}
.hjc-confirm-pop-leave-to {
transform: scale(0.95);
opacity: 0;
}
</style>