feat: 新增可配置的路径前缀以支持子路径部署

添加前后端环境变量配置示例,新增后端路径前缀配置项,更新后端路由与静态文件挂载逻辑,调整前端代理配置与 API 调用代码,适配子路径部署场景。
This commit is contained in:
2026-05-21 20:56:22 +08:00
parent fd1aea1687
commit ded7ea1228
8 changed files with 130 additions and 84 deletions

View File

@@ -1,5 +1,19 @@
const TOKEN_KEY = "auth_token";
const env = (import.meta as any).env || {};
const API_PREFIX: string = (env.VITE_API_PREFIX as string) || "/api";
const APP_BASE_URL: string = (env.BASE_URL as string) || "/";
function joinPrefix(prefix: string, path: string) {
const p = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
const s = path.startsWith("/") ? path : `/${path}`;
return `${p}${s}`;
}
function apiPath(path: string) {
return joinPrefix(API_PREFIX, path);
}
export function getToken(): string | null {
return localStorage.getItem(TOKEN_KEY);
}
@@ -22,11 +36,11 @@ async function fetchApi<T>(path: string, options: RequestInit = {}): Promise<T>
headers["Authorization"] = `Bearer ${token}`;
}
const res = await fetch(path, { ...options, headers });
const res = await fetch(apiPath(path), { ...options, headers });
if (res.status === 401) {
clearToken();
window.location.href = "/";
window.location.href = APP_BASE_URL;
throw new Error("登录已过期,请重新登录");
}
@@ -48,21 +62,21 @@ interface LoginResponse {
}
export async function loginByPhone(phone: string, smsCode: string): Promise<LoginResponse> {
return fetchApi<LoginResponse>("/api/auth/phone-login", {
return fetchApi<LoginResponse>("/auth/phone-login", {
method: "POST",
body: JSON.stringify({ phone, sms_code: smsCode }),
});
}
export async function loginByWechat(code: string): Promise<LoginResponse> {
return fetchApi<LoginResponse>("/api/auth/wechat-login", {
return fetchApi<LoginResponse>("/auth/wechat-login", {
method: "POST",
body: JSON.stringify({ code }),
});
}
export async function loginByDemo(): Promise<LoginResponse> {
return fetchApi<LoginResponse>("/api/auth/demo-login", {
return fetchApi<LoginResponse>("/auth/demo-login", {
method: "POST",
});
}
@@ -91,7 +105,7 @@ export interface UserProfileDTO {
}
export async function getUserProfile(): Promise<UserProfileDTO> {
return fetchApi<UserProfileDTO>("/api/user/profile");
return fetchApi<UserProfileDTO>("/user/profile");
}
export interface OnboardingData {
@@ -109,14 +123,14 @@ export interface OnboardingData {
}
export async function submitOnboarding(data: OnboardingData): Promise<UserProfileDTO> {
return fetchApi<UserProfileDTO>("/api/user/onboarding", {
return fetchApi<UserProfileDTO>("/user/onboarding", {
method: "POST",
body: JSON.stringify(data),
});
}
export async function updateUserProfile(data: Record<string, unknown>): Promise<UserProfileDTO> {
return fetchApi<UserProfileDTO>("/api/user/profile", {
return fetchApi<UserProfileDTO>("/user/profile", {
method: "PUT",
body: JSON.stringify(data),
});
@@ -151,7 +165,7 @@ export async function uploadReportImage(file: File): Promise<UploadReportRespons
const headers: Record<string, string> = {};
if (token) headers["Authorization"] = `Bearer ${token}`;
const res = await fetch("/api/report/upload", {
const res = await fetch(apiPath("/report/upload"), {
method: "POST",
headers,
body: formData,
@@ -159,7 +173,7 @@ export async function uploadReportImage(file: File): Promise<UploadReportRespons
if (res.status === 401) {
clearToken();
window.location.href = "/";
window.location.href = APP_BASE_URL;
throw new Error("登录已过期,请重新登录");
}
if (!res.ok) {
@@ -189,7 +203,7 @@ export async function addImageToReport(reportId: string, file: File): Promise<Ad
const headers: Record<string, string> = {};
if (token) headers["Authorization"] = `Bearer ${token}`;
const res = await fetch(`/api/report/${reportId}/add-image`, {
const res = await fetch(apiPath(`/report/${reportId}/add-image`), {
method: "POST",
headers,
body: formData,
@@ -197,7 +211,7 @@ export async function addImageToReport(reportId: string, file: File): Promise<Ad
if (res.status === 401) {
clearToken();
window.location.href = "/";
window.location.href = APP_BASE_URL;
throw new Error("登录已过期,请重新登录");
}
if (!res.ok) {
@@ -237,7 +251,7 @@ export interface ReportResponse {
}
export async function verifyReport(reportId: string, data: ReportVerifyRequest): Promise<ReportResponse> {
return fetchApi<ReportResponse>(`/api/report/${reportId}/verify`, {
return fetchApi<ReportResponse>(`/report/${reportId}/verify`, {
method: "PUT",
body: JSON.stringify(data),
});
@@ -254,11 +268,11 @@ export interface ReportListItem {
}
export async function getReportList(limit = 50): Promise<ReportListItem[]> {
return fetchApi<ReportListItem[]>(`/api/report/list?limit=${limit}`);
return fetchApi<ReportListItem[]>(`/report/list?limit=${limit}`);
}
export async function getReport(reportId: string): Promise<ReportResponse> {
return fetchApi<ReportResponse>(`/api/report/${reportId}`);
return fetchApi<ReportResponse>(`/report/${reportId}`);
}
// ─── Analysis ───
@@ -297,13 +311,13 @@ export interface AnalysisResponse {
}
export async function generateAnalysis(reportId: string): Promise<AnalysisResponse> {
return fetchApi<AnalysisResponse>(`/api/analysis/${reportId}/generate`, {
return fetchApi<AnalysisResponse>(`/analysis/${reportId}/generate`, {
method: "POST",
});
}
export async function getAnalysis(reportId: string): Promise<AnalysisResponse> {
return fetchApi<AnalysisResponse>(`/api/analysis/${reportId}`);
return fetchApi<AnalysisResponse>(`/analysis/${reportId}`);
}
// ─── Chat ───
@@ -319,14 +333,14 @@ export interface ChatHistoryResponse {
}
export async function sendChatMessage(reportId: string, content: string): Promise<ChatMessageResponse> {
return fetchApi<ChatMessageResponse>(`/api/chat/${reportId}/message`, {
return fetchApi<ChatMessageResponse>(`/chat/${reportId}/message`, {
method: "POST",
body: JSON.stringify({ content }),
});
}
export async function getChatHistory(reportId: string): Promise<ChatHistoryResponse> {
return fetchApi<ChatHistoryResponse>(`/api/chat/${reportId}/history`);
return fetchApi<ChatHistoryResponse>(`/chat/${reportId}/history`);
}
// ─── Health Records ───
@@ -337,7 +351,7 @@ export interface HealthOverviewResponse {
}
export async function getHealthOverview(): Promise<HealthOverviewResponse> {
return fetchApi<HealthOverviewResponse>("/api/health-records/overview");
return fetchApi<HealthOverviewResponse>("/health-records/overview");
}
export interface GrowthCurveResponse {
@@ -345,7 +359,7 @@ export interface GrowthCurveResponse {
}
export async function getGrowthCurve(): Promise<GrowthCurveResponse> {
return fetchApi<GrowthCurveResponse>("/api/health-records/growth-curve");
return fetchApi<GrowthCurveResponse>("/health-records/growth-curve");
}
export interface TimelineResponse {
@@ -361,7 +375,7 @@ export interface TimelineResponse {
}
export async function getTimeline(): Promise<TimelineResponse> {
return fetchApi<TimelineResponse>("/api/health-records/timeline");
return fetchApi<TimelineResponse>("/health-records/timeline");
}
export interface IndicatorTrendPoint {
@@ -385,7 +399,7 @@ export interface IndicatorTrendsResponse {
}
export async function getIndicatorTrends(): Promise<IndicatorTrendsResponse> {
return fetchApi<IndicatorTrendsResponse>("/api/health-records/indicator-trends");
return fetchApi<IndicatorTrendsResponse>("/health-records/indicator-trends");
}
export interface ReportTypeTrendPoint {
@@ -414,7 +428,7 @@ export interface ReportTypeTrendsResponse {
}
export async function getReportTypeTrends(): Promise<ReportTypeTrendsResponse> {
return fetchApi<ReportTypeTrendsResponse>("/api/health-records/report-type-trends");
return fetchApi<ReportTypeTrendsResponse>("/health-records/report-type-trends");
}
// ─── Calendar ───
@@ -437,11 +451,11 @@ export interface CheckupPlanResponse {
}
export async function getCheckupPlan(): Promise<CheckupPlanResponse> {
return fetchApi<CheckupPlanResponse>("/api/calendar/plan");
return fetchApi<CheckupPlanResponse>("/calendar/plan");
}
export async function completeCheckup(week: number): Promise<{ message: string }> {
return fetchApi<{ message: string }>(`/api/calendar/plan/${week}/complete`, {
return fetchApi<{ message: string }>(`/calendar/plan/${week}/complete`, {
method: "PUT",
});
}
@@ -454,7 +468,7 @@ export interface DoctorQuestionsResponse {
}
export async function getDoctorQuestions(reportId: string): Promise<DoctorQuestionsResponse> {
return fetchApi<DoctorQuestionsResponse>(`/api/doctor/questions/${reportId}`);
return fetchApi<DoctorQuestionsResponse>(`/doctor/questions/${reportId}`);
}
export interface Doctor {
@@ -473,11 +487,11 @@ export interface Doctor {
}
export async function getExperts(): Promise<{ doctors: Doctor[] }> {
return fetchApi<{ doctors: Doctor[] }>("/api/doctor/experts");
return fetchApi<{ doctors: Doctor[] }>("/doctor/experts");
}
export async function getExpertDetail(doctorId: string): Promise<Doctor> {
return fetchApi<Doctor>(`/api/doctor/experts/${doctorId}`);
return fetchApi<Doctor>(`/doctor/experts/${doctorId}`);
}
// ─── Share ───
@@ -503,7 +517,7 @@ export async function generateShareData(
reportId: string,
options: { privacy_mode?: boolean; include_details?: boolean } = {}
): Promise<ShareDataResponse> {
return fetchApi<ShareDataResponse>(`/api/share/${reportId}/generate`, {
return fetchApi<ShareDataResponse>(`/share/${reportId}/generate`, {
method: "POST",
body: JSON.stringify({ privacy_mode: true, include_details: false, ...options }),
});