From 6c40165f869a2ff37967f114c52d06ed37322b6b Mon Sep 17 00:00:00 2001 From: "weicw1996@qq.com" Date: Fri, 18 Sep 2026 17:09:39 +0800 Subject: [PATCH] =?UTF-8?q?feat(hjc-web):=20=E9=99=84=E4=BB=B6=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E6=94=B9=E4=B8=BA=E3=80=8C=E7=82=B9=E5=87=BB=E6=97=B6?= =?UTF-8?q?=E5=8F=96=E7=A5=A8=E5=86=8D=E4=B8=8B=E8=BD=BD=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 一站式的静态文件服务已加鉴权,附件不再直连(工作区根目录 docs/adr/0007): 后端下发的地址现在是**本平台资源地址**(kind + 订单号/项目编号 + 序号), 既没有文件路径(路径由后端查,防越权)也没有票据。 - 新增 Nitro 透传 server/api/tender/attachment-ticket.get.ts:带 Authorization (请求头或 hjc_token cookie)+ TenantId 调后端取票,原样透传 ApiResult。 - HjcTenderFiles.vue:`` → 按钮 + 点击时取票 + `window.location.href`。 用 location 而不是 window.open:取票是异步的,异步之后弹窗会被浏览器拦截; 附件响应带 Content-Disposition: attachment,下载完仍留在本页。 该组件同时被订单列表/订单详情/项目详情(公告附件)复用,一处改动覆盖三处。 - 错误提示直接复用后端 401/403 约定,组件内联展示,不引入新的 toast 依赖。 校验:npm run build 通过(新增路由已出现在构建产物中)。 --- app/components/HjcTenderFiles.vue | 106 +++++++++++++++++---- server/api/tender/attachment-ticket.get.ts | 53 +++++++++++ 2 files changed, 139 insertions(+), 20 deletions(-) create mode 100644 server/api/tender/attachment-ticket.get.ts diff --git a/app/components/HjcTenderFiles.vue b/app/components/HjcTenderFiles.vue index 178f940..6713c9c 100644 --- a/app/components/HjcTenderFiles.vue +++ b/app/components/HjcTenderFiles.vue @@ -22,40 +22,47 @@ - {{ nameOf(file) }} - + - - - 下载 - + {{ busyIndex === idx ? '正在准备…' : '下载' }} + + +

{{ errorText }}

diff --git a/server/api/tender/attachment-ticket.get.ts b/server/api/tender/attachment-ticket.get.ts new file mode 100644 index 0000000..2089aeb --- /dev/null +++ b/server/api/tender/attachment-ticket.get.ts @@ -0,0 +1,53 @@ +import { $fetch } from 'ofetch' +import { createError, defineEventHandler, getHeader, getCookie, getQuery } from 'h3' +import { useRuntimeConfig } from '#imports' +import { getTenantFromContext } from '../../utils/tenant' + +/** + * 取附件下载票据(一次性、短时)。 + * GET /api/tender/attachment-ticket?kind=tender&orderNo=xxx&index=0 + * GET /api/tender/attachment-ticket?kind=bulletin&projectNo=xxx&index=0 + * + * 为什么要它:一站式的静态文件服务加了鉴权,附件不再直连,改由后端反代 + * (工作区根目录 `docs/adr/0007-attachment-download-via-platform-proxy.md`)。 + * 下发给前端的附件地址是**本平台的资源地址**(只有 kind/订单号或项目编号/序号, + * 没有路径、也没有票据);**点击下载时**才来这里换一张票据,再用票据去 + * `{modulesApiBase}/api/hjc/attachment?ticket=…` 取文件。 + * + * 为什么票据要现取而不能直接放进列表里的地址:票据很短命,"页面开着放一会儿再点" + * 会撞过期;现取则永远新鲜(ADR 0007 补记)。 + * + * 约定:原样透传 ApiResult{code,message,data},HTTP 状态码恒为 200。 + * `code=401`(未登录)与 `code=403`(未支付/无权/已下架)都是**业务码**, + * 前端必须按 code 判定——401 要清凭据跳登录,403 只提示。 + */ +export default defineEventHandler(async (event) => { + const config = useRuntimeConfig() + const ctx = getTenantFromContext(event, config) + const { kind, orderNo, projectNo, index } = getQuery(event) + const cookieToken = getCookie(event, 'hjc_token') + const auth = getHeader(event, 'authorization') || (cookieToken ? `Bearer ${cookieToken}` : null) + + try { + return await $fetch('/hjc/attachment/ticket', { + baseURL: config.public.modulesApiBase, + method: 'GET', + headers: { + TenantId: ctx.tenantId, + ...(auth ? { Authorization: auth } : {}) + }, + query: { + TenantId: ctx.tenantId, + kind, + index, + ...(orderNo ? { orderNo } : {}), + ...(projectNo ? { projectNo } : {}) + } + }) + } catch (error: any) { + throw createError({ + statusCode: error?.statusCode || error?.response?.status || 502, + statusMessage: error?.statusMessage || 'Failed to fetch attachment ticket' + }) + } +})