diff --git a/frontend/src/composables/useApi.ts b/frontend/src/composables/useApi.ts index 78c26df..4b143c6 100644 --- a/frontend/src/composables/useApi.ts +++ b/frontend/src/composables/useApi.ts @@ -58,6 +58,19 @@ const api: AxiosInstance = axios.create({ withCredentials: true, }) +// 백엔드 오리진 — 다운로드 링크용. axios 와 달리 앵커는 baseURL 을 쓰지 않으므로, +// 백엔드가 내려준 루트 상대 경로('/api/...')에 백엔드 오리진을 붙여 절대 URL 로 만든다. +// (dev 는 프론트 5273 / 백엔드 3100 로 오리진이 달라, 안 붙이면 프론트로 가서 404 페이지가 뜬다) +const API_ORIGIN = new URL( + import.meta.env.VITE_API_BASE_URL || '/api', + window.location.origin, +).origin + +export function toAbsoluteApiUrl(path: string): string { + if (!path || /^https?:\/\//i.test(path)) return path + return `${API_ORIGIN}${path.startsWith('/') ? '' : '/'}${path}` +} + // 요청 인터셉터 — CSRF, 추가 헤더 등 필요 시 확장 api.interceptors.request.use( (config: InternalAxiosRequestConfig) => config, diff --git a/frontend/src/stores/task.store.ts b/frontend/src/stores/task.store.ts index 3d60ab5..363cba4 100644 --- a/frontend/src/stores/task.store.ts +++ b/frontend/src/stores/task.store.ts @@ -1,6 +1,7 @@ import { computed, ref } from 'vue' import { defineStore } from 'pinia' import { useTask } from '@/composables/useTask' +import { toAbsoluteApiUrl } from '@/composables/useApi' import { DEFAULT_PAGE_SIZE } from '@/types/pagination' import { escapeHtml, @@ -75,7 +76,8 @@ function toAttachmentView(a: ApiAttachment): AttachmentView { name: a.name, size: formatBytes(a.size), kind: a.kind, - url: a.url, + // 다운로드 앵커용 절대 URL(백엔드 오리진) — 상대경로면 프론트로 가서 404 페이지가 됨 + url: toAbsoluteApiUrl(a.url), } }