import { ref } from 'vue' /** * 「点击下载」的共用逻辑:**先换票据、再取文件**。 * * 背景:一站式的静态文件服务已加鉴权,附件/发票不再直连(工作区根目录 * `docs/adr/0007-attachment-download-via-platform-proxy.md`)。后端下发的地址是 * **本平台的资源地址**——只有资源标识(kind + 订单号/项目编号/发票 id + 序号), * 既没有文件路径(路径由后端查,防越权),也没有票据。票据是**点击时**现取的 * (ADR 0007 补记:现取的永远新鲜,不会出现"页面放久了点不动")。 * * 抽出来的理由:标书附件、公告附件、电子发票三处都要走同一条链路,各写一份必然分叉 * ——与 `orderStatus.ts`、`hjcCanActOnOrder` 同一动机。 */ export function useAttachmentDownload() { /** 正在准备的文件标识(用于禁用按钮、显示"正在准备…"),同一时刻只允许一个 */ const busyKey = ref('') const errorText = ref('') /** * 从资源地址里取出取票所需的标识。 * * 地址形态由后端固定生成,只有这几个参数;解析不出来就说明地址不是本平台的资源地址 * (例如历史数据里的 OSS 直链),那种直接按普通链接打开即可。 */ function resourceQueryOf(url: string): Record | null { const query = String(url || '').split('?')[1] if (!query) return null const params = new URLSearchParams(query) const kind = params.get('kind') const index = params.get('index') if (!kind || index === null) return null const target: Record = { kind, index } for (const key of ['orderNo', 'projectNo', 'invoiceId']) { const value = params.get(key) if (value) target[key] = value } // 至少要有一个资源标识,否则后端也无从查起 return target.orderNo || target.projectNo || target.invoiceId ? target : null } /** 票据地址是相对后端的(`/api/hjc/attachment?ticket=…`),补上后端域名 */ function absoluteApiUrl(url: string) { if (/^https?:\/\//i.test(url)) return url const base = String(useRuntimeConfig().public.modulesApiBase || '').replace(/\/api\/?$/, '') return base + (url.startsWith('/') ? url : `/${url}`) } /** * 取票并打开文件。 * * @param file 后端下发的 `{name, url}`(url 是资源地址) * @param key 用于标识"哪一个正在准备"(同一列表里区分按钮) */ async function download(file: { name?: string; url?: string }, key?: string) { errorText.value = '' const url = String(file?.url || '') const query = resourceQueryOf(url) // 不是本平台的资源地址(历史 OSS 直链):原样打开,没必要绕一圈 if (!query) { if (/^https?:\/\//i.test(url)) { window.location.href = url return } errorText.value = '文件地址不可用,请刷新页面后重试' return } const busy = key || url busyKey.value = busy try { const res: any = await $fetch('/api/tender/attachment-ticket', { query }) if (!res || res.code !== 0 || !res.data?.url) { errorText.value = res?.message || '获取下载链接失败,请稍后重试' return } // 用 location 而不是 window.open:取票是异步的,异步之后再弹窗会被浏览器拦截; // 而附件响应带 Content-Disposition: attachment,浏览器下载完仍留在本页。 window.location.href = absoluteApiUrl(String(res.data.url)) } catch (error: any) { errorText.value = error?.data?.message || error?.statusMessage || error?.message || '下载失败,请稍后重试' } finally { busyKey.value = '' } } return { busyKey, errorText, download } }