Files
hjc-web/server/api/tender/attachment-ticket.get.ts
T
weicw1996 6c40165f86 feat(hjc-web): 附件下载改为「点击时取票再下载」
一站式的静态文件服务已加鉴权,附件不再直连(工作区根目录 docs/adr/0007):
后端下发的地址现在是**本平台资源地址**(kind + 订单号/项目编号 + 序号),
既没有文件路径(路径由后端查,防越权)也没有票据。

- 新增 Nitro 透传 server/api/tender/attachment-ticket.get.ts:带 Authorization
  (请求头或 hjc_token cookie)+ TenantId 调后端取票,原样透传 ApiResult。
- HjcTenderFiles.vue:`<a :href>` → 按钮 + 点击时取票 + `window.location.href`。
  用 location 而不是 window.open:取票是异步的,异步之后弹窗会被浏览器拦截;
  附件响应带 Content-Disposition: attachment,下载完仍留在本页。
  该组件同时被订单列表/订单详情/项目详情(公告附件)复用,一处改动覆盖三处。
- 错误提示直接复用后端 401/403 约定,组件内联展示,不引入新的 toast 依赖。

校验:npm run build 通过(新增路由已出现在构建产物中)。
2026-09-18 17:09:52 +08:00

54 lines
2.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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'
})
}
})