From 2e847b5e09e2f1d348313a827d3d84e37df226a3 Mon Sep 17 00:00:00 2001 From: "weicw1996@qq.com" Date: Thu, 17 Sep 2026 18:09:27 +0800 Subject: [PATCH] =?UTF-8?q?feat(hjc-web):=20=E6=96=B0=E5=BB=BA=E8=AE=A2?= =?UTF-8?q?=E5=8D=95=E8=AF=A6=E6=83=85=E9=A1=B5=EF=BC=8C=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E4=B8=8E=E8=AF=A6=E6=83=85=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E5=B7=B2=E6=94=AF=E4=BB=98=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E7=9A=84=E6=A0=87=E4=B9=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 此前 PC 没有订单详情页,标书下载只有两句文案(`.scratch/hjc-web-order-detail/01`)。 - 新增 `app/pages/tender/orders/[id].vue`:订单状态、标书附件、待支付操作区、 标书/支付/购买人信息;取数走 `useRequestFetch()`(裸 `$fetch` 在 SSR 不转发 Cookie, 会把用户登出) - `app/pages/tender/orders.vue` → `orders/index.vue`:Nuxt 里同名 file+目录会生成父子路由, 详情页会被父页吞掉,必须让列表页是 `index.vue` - 列表页每张卡片加「标书附件」(仅已支付)与「查看详情」,不整卡可点(卡内已有按钮与链接) - 新增 `app/components/HjcTenderFiles.vue` 与代理 `server/api/tender/order-detail.get.ts` - 附件地址来自后端按 `payStatus=1` 下发的 `tenderFiles`,前端不做权限判断、不自己拼地址 规格与验收:`.scratch/hjc-tender-download/` --- app/components/HjcTenderFiles.vue | 77 +++++ app/composables/useHjcAuth.ts | 2 +- app/pages/tender/favorites.vue | 2 +- app/pages/tender/orders/[id].vue | 289 ++++++++++++++++++ .../tender/{orders.vue => orders/index.vue} | 51 +++- .../template-07/pages/TenderDetail.vue | 2 +- app/types/tender.ts | 42 +++ server/api/tender/order-detail.get.ts | 42 +++ 8 files changed, 490 insertions(+), 17 deletions(-) create mode 100644 app/components/HjcTenderFiles.vue create mode 100644 app/pages/tender/orders/[id].vue rename app/pages/tender/{orders.vue => orders/index.vue} (76%) create mode 100644 server/api/tender/order-detail.get.ts diff --git a/app/components/HjcTenderFiles.vue b/app/components/HjcTenderFiles.vue new file mode 100644 index 0000000..178f940 --- /dev/null +++ b/app/components/HjcTenderFiles.vue @@ -0,0 +1,77 @@ + + + diff --git a/app/composables/useHjcAuth.ts b/app/composables/useHjcAuth.ts index 73f8973..d23c35e 100644 --- a/app/composables/useHjcAuth.ts +++ b/app/composables/useHjcAuth.ts @@ -72,7 +72,7 @@ export function useHjcAuth() { // 因此凡是**服务端渲染期间**发出的、需要登录态的站内请求,必须用 // `useRequestFetch()` 而不是裸 `$fetch`——裸 $fetch 不会把浏览器 Cookie 转发给 // 站内 /api 代理,代理会变成匿名调用并拿到 code=401,从而被这里误判为 - // 「凭据失效」:刷新页面 = 把用户登出。参见 app/pages/tender/orders.vue。 + // 「凭据失效」:刷新页面 = 把用户登出。参见 app/pages/tender/orders/index.vue。 clearCredentials() await navigateTo(`/login?redirect=${encodeURIComponent(redirect)}`) return { handled: true } diff --git a/app/pages/tender/favorites.vue b/app/pages/tender/favorites.vue index e5ac999..00b51d6 100644 --- a/app/pages/tender/favorites.vue +++ b/app/pages/tender/favorites.vue @@ -90,7 +90,7 @@ const { isLoggedIn, handleAuthCode } = useHjcAuth() * **不会**把浏览器请求里的 Cookie 转发给站内 `/api` 代理。代理拿不到 `hjc_token` * 就等于匿名调用,后端按契约返回 `code=401`;`handleAuthCode` 据此判定「凭据失效」 * → 在服务端清 Token → 结果是**刷新「我的收藏」= 把自己登出**。 - * (同一段坑的完整说明见 app/pages/tender/orders.vue:77-88。) + * (同一段坑的完整说明见 app/pages/tender/orders/index.vue:101-110。) * * 客户端侧 `useRequestFetch()` 等价于 `globalThis.$fetch`,行为不变。 */ diff --git a/app/pages/tender/orders/[id].vue b/app/pages/tender/orders/[id].vue new file mode 100644 index 0000000..465dfc0 --- /dev/null +++ b/app/pages/tender/orders/[id].vue @@ -0,0 +1,289 @@ + + + diff --git a/app/pages/tender/orders.vue b/app/pages/tender/orders/index.vue similarity index 76% rename from app/pages/tender/orders.vue rename to app/pages/tender/orders/index.vue index 493deea..1136437 100644 --- a/app/pages/tender/orders.vue +++ b/app/pages/tender/orders/index.vue @@ -30,24 +30,47 @@ -
- +
+

标书附件

+ +
+ +
+ - 去支付 + 查看详情 + + +
+ + + 去支付 + +
diff --git a/app/templates/template-07/pages/TenderDetail.vue b/app/templates/template-07/pages/TenderDetail.vue index fa98b4d..fe0cd58 100644 --- a/app/templates/template-07/pages/TenderDetail.vue +++ b/app/templates/template-07/pages/TenderDetail.vue @@ -123,7 +123,7 @@ const { isLoggedIn, handleAuthCode } = useHjcAuth() * 详情响应里内联了 `favorited`,而站内代理只有在 **Cookie 被转发** 时才认得出登录态: * Nuxt 只在 `useRequestFetch` / `useFetch` 里做转发,裸 `$fetch` 在 SSR 期间不转发 Cookie, * 代理就是匿名调用,后端按契约返回 `favorited = false` —— 已收藏的项目首屏会显示成「未收藏」。 - * (同一个坑的另一种表现见 app/pages/tender/orders.vue:77-88:那次是刷新把自己登出。) + * (同一个坑的另一种表现见 app/pages/tender/orders/index.vue:101-110:那次是刷新把自己登出。) * * 客户端侧 `useRequestFetch()` 等价于 `globalThis.$fetch`,行为不变。 */ diff --git a/app/types/tender.ts b/app/types/tender.ts index 59cdb83..f2dd3ed 100644 --- a/app/types/tender.ts +++ b/app/types/tender.ts @@ -86,6 +86,48 @@ export interface TenderPage { count: number } +/** + * 标书附件(对应 mp-java `HjcTenderFileVo`)。 + * + * 由**后端**把项目的 `tender_file`(JSON 数组或逗号分隔)解析成结构化列表, + * 前端只渲染、不解析也不判权限。 + */ +export interface HjcTenderFile { + /** 展示名;后端取不到时会兜底成「标书附件」 */ + name?: string + /** 可下载地址 */ + url: string +} + +/** + * 订单(对应 mp-java `HjcOrder`,只列页面用得到的字段)。 + * + * `tenderFiles` 是**付费内容**:只有 `payStatus === 1`(已支付)的订单,后端才会下发; + * 待支付 / 支付失败 / 已退款一律为 `null`(空数组表示已支付但项目没有附件)。 + * 前端**不根据 payStatus 自己拼地址**——地址没下发就是拿不到。 + */ +export interface HjcOrder { + id: number + orderNo: string + projectId?: number + projectNo?: string + projectName?: string + enterpriseName?: string + contactName?: string + contactPhone?: string + contactEmail?: string + quantity?: number + unitPrice?: number + totalAmount?: number + payMethod?: string + payTime?: string + payStatus?: number + orderStatus?: number + createTime?: string + /** 仅已支付订单由后端下发 */ + tenderFiles?: HjcTenderFile[] | null +} + /** 登录用户(对应后端 common.system.User 关键字段) */ export interface HjcUser { userId: number diff --git a/server/api/tender/order-detail.get.ts b/server/api/tender/order-detail.get.ts new file mode 100644 index 0000000..335882c --- /dev/null +++ b/server/api/tender/order-detail.get.ts @@ -0,0 +1,42 @@ +import { $fetch } from 'ofetch' +import { createError, defineEventHandler, getHeader, getCookie, getQuery } from 'h3' +import { useRuntimeConfig } from '#imports' +import { getTenantFromContext } from '../../utils/tenant' + +/** + * 订单详情(含已支付订单的标书附件) + * GET /api/tender/order-detail?id=16 + * 代理到 mp-api /api/hjc/order/{id};需登录态 + 订单归属(后端按 401/403 区分)。 + * + * 为什么要这个代理:`/hjc/order/{id}` 会带上 `project`,且对**已支付**订单附带 + * `tenderFiles`(后端解析好的标书附件列表)——标书下载就靠它。未支付订单两张表都不给, + * 所以页面上「没有附件」不是前端藏起来的,而是后端压根没下发地址。 + * + * 约定:原样透传 ApiResult{code,message,data}。HTTP 状态码恒为 200, + * 「未登录/token 失效」是 code=401、「无权查看该订单」是 code=403,前端必须按 code 判定; + * 故这里**不能**只取 data,否则登录态失败会被当成「订单不存在」。 + */ +export default defineEventHandler(async (event) => { + const config = useRuntimeConfig() + const ctx = getTenantFromContext(event, config) + const { id } = getQuery(event) + const cookieToken = getCookie(event, 'hjc_token') + const auth = getHeader(event, 'authorization') || (cookieToken ? `Bearer ${cookieToken}` : null) + + try { + return await $fetch(`/hjc/order/${encodeURIComponent(String(id ?? ''))}`, { + baseURL: config.public.modulesApiBase, + method: 'GET', + headers: { + TenantId: ctx.tenantId, + ...(auth ? { Authorization: auth } : {}) + }, + query: { TenantId: ctx.tenantId } + }) + } catch (error: any) { + throw createError({ + statusCode: error?.statusCode || error?.response?.status || 502, + statusMessage: error?.statusMessage || 'Failed to fetch order detail' + }) + } +})