feat: 新增可配置的路径前缀以支持子路径部署
添加前后端环境变量配置示例,新增后端路径前缀配置项,更新后端路由与静态文件挂载逻辑,调整前端代理配置与 API 调用代码,适配子路径部署场景。
This commit is contained in:
@@ -1,29 +1,37 @@
|
|||||||
# MongoDB
|
# Path Prefix
|
||||||
MONGODB_URL=mongodb://localhost:27017
|
API_PREFIX=/app_03/api
|
||||||
MONGODB_DB_NAME=pregnancy_report
|
UPLOADS_PREFIX=/uploads
|
||||||
|
|
||||||
|
# MongoDB (通过 SSH 隧道连接到服务器)
|
||||||
|
# MONGODB_URL=mongodb+srv://shutiaozhao127_db_user:YEJzZUi0nbEuFYL3@cluster0.bkztgqx.mongodb.net/?appName=Cluster0
|
||||||
|
# MONGODB_DB_NAME=pregnancy_report
|
||||||
|
MONGODB_URL=mongodb://root:mgdb4link@localhost:27017/?authSource=admin
|
||||||
|
MONGODB_DB_NAME=app_03_dev
|
||||||
|
|
||||||
# JWT
|
# JWT
|
||||||
JWT_SECRET_KEY=your-secret-key-change-this
|
JWT_SECRET_KEY=pregnancy_ai_demo_666888
|
||||||
JWT_ALGORITHM=HS256
|
JWT_ALGORITHM=HS256
|
||||||
JWT_EXPIRE_MINUTES=10080
|
JWT_EXPIRE_MINUTES=10080
|
||||||
|
|
||||||
# WeChat Mini Program
|
# WeChat Mini Program
|
||||||
WECHAT_APP_ID=your-wechat-app-id
|
WECHAT_APP_ID=
|
||||||
WECHAT_APP_SECRET=your-wechat-app-secret
|
WECHAT_APP_SECRET=
|
||||||
|
|
||||||
# Claude AI
|
# AI API (硅基流动 - OpenAI 兼容接口)
|
||||||
ANTHROPIC_API_KEY=your-anthropic-api-key
|
AI_API_KEY=sk-pnbkufcvwbnmzcwdhwzecdgpiqqsumtsnfojtozmmrfqiuil
|
||||||
ANTHROPIC_BASE_URL=
|
AI_BASE_URL=https://api.siliconflow.cn/v1
|
||||||
AI_MODEL_NAME=claude-sonnet-4-20250514
|
AI_VISION_MODEL=Qwen/Qwen2.5-VL-72B-Instruct
|
||||||
|
AI_TEXT_MODEL=Qwen/Qwen3-235B-A22B-Instruct-2507
|
||||||
|
|
||||||
# CORS(多个域名逗号分隔,* 表示允许所有)
|
# CORS(多个域名逗号分隔,* 表示允许所有)
|
||||||
CORS_ORIGINS=*
|
CORS_ORIGINS=*
|
||||||
|
|
||||||
|
|
||||||
# File Upload
|
# File Upload
|
||||||
UPLOAD_DIR=./uploads
|
UPLOAD_DIR=./uploads
|
||||||
MAX_FILE_SIZE=10485760
|
MAX_FILE_SIZE=10485760
|
||||||
|
|
||||||
# Server
|
# Server
|
||||||
HOST=0.0.0.0
|
HOST=0.0.0.0
|
||||||
PORT=8000
|
PORT=8030
|
||||||
DEBUG=true
|
DEBUG=true
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ from pydantic_settings import BaseSettings
|
|||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
|
# Path Prefix
|
||||||
|
api_prefix: str = "/api"
|
||||||
|
uploads_prefix: str = "/uploads"
|
||||||
|
|
||||||
# MongoDB
|
# MongoDB
|
||||||
mongodb_url: str = "mongodb://localhost:27017"
|
mongodb_url: str = "mongodb://localhost:27017"
|
||||||
mongodb_db_name: str = "pregnancy_report"
|
mongodb_db_name: str = "pregnancy_report"
|
||||||
|
|||||||
@@ -48,6 +48,14 @@ app = FastAPI(
|
|||||||
lifespan=lifespan,
|
lifespan=lifespan,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
api_prefix = settings.api_prefix.rstrip("/") or ""
|
||||||
|
if api_prefix and not api_prefix.startswith("/"):
|
||||||
|
api_prefix = f"/{api_prefix}"
|
||||||
|
|
||||||
|
uploads_prefix = settings.uploads_prefix.rstrip("/") or ""
|
||||||
|
if uploads_prefix and not uploads_prefix.startswith("/"):
|
||||||
|
uploads_prefix = f"/{uploads_prefix}"
|
||||||
|
|
||||||
# CORS 配置
|
# CORS 配置
|
||||||
cors_origins = (
|
cors_origins = (
|
||||||
["*"]
|
["*"]
|
||||||
@@ -88,20 +96,20 @@ async def global_exception_handler(request: Request, exc: Exception):
|
|||||||
content={"detail": "服务器内部错误,请稍后重试"},
|
content={"detail": "服务器内部错误,请稍后重试"},
|
||||||
)
|
)
|
||||||
|
|
||||||
app.mount("/uploads", StaticFiles(directory=settings.upload_dir), name="uploads")
|
app.mount(uploads_prefix or "/uploads", StaticFiles(directory=settings.upload_dir), name="uploads")
|
||||||
|
|
||||||
app.include_router(auth.router, prefix="/api/auth", tags=["认证"])
|
app.include_router(auth.router, prefix=f"{api_prefix}/auth", tags=["认证"])
|
||||||
app.include_router(user.router, prefix="/api/user", tags=["用户"])
|
app.include_router(user.router, prefix=f"{api_prefix}/user", tags=["用户"])
|
||||||
app.include_router(report.router, prefix="/api/report", tags=["报告"])
|
app.include_router(report.router, prefix=f"{api_prefix}/report", tags=["报告"])
|
||||||
app.include_router(analysis.router, prefix="/api/analysis", tags=["AI解读"])
|
app.include_router(analysis.router, prefix=f"{api_prefix}/analysis", tags=["AI解读"])
|
||||||
app.include_router(chat.router, prefix="/api/chat", tags=["AI对话"])
|
app.include_router(chat.router, prefix=f"{api_prefix}/chat", tags=["AI对话"])
|
||||||
app.include_router(health_records.router, prefix="/api/health-records", tags=["健康档案"])
|
app.include_router(health_records.router, prefix=f"{api_prefix}/health-records", tags=["健康档案"])
|
||||||
app.include_router(calendar.router, prefix="/api/calendar", tags=["产检日历"])
|
app.include_router(calendar.router, prefix=f"{api_prefix}/calendar", tags=["产检日历"])
|
||||||
app.include_router(doctor.router, prefix="/api/doctor", tags=["专家咨询"])
|
app.include_router(doctor.router, prefix=f"{api_prefix}/doctor", tags=["专家咨询"])
|
||||||
app.include_router(share.router, prefix="/api/share", tags=["分享"])
|
app.include_router(share.router, prefix=f"{api_prefix}/share", tags=["分享"])
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/health")
|
@app.get(f"{api_prefix}/health")
|
||||||
async def health_check():
|
async def health_check():
|
||||||
return {"status": "ok", "service": "孕育无忧 API"}
|
return {"status": "ok", "service": "孕育无忧 API"}
|
||||||
|
|
||||||
|
|||||||
@@ -225,6 +225,7 @@ textarea{width:100%;padding:10px;border:1px solid #ddd;border-radius:8px;font-si
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
const API = '';
|
const API = '';
|
||||||
|
const API_PREFIX = window.location.pathname.startsWith('/app_03') ? '/app_03/api' : '/api';
|
||||||
let TOKEN = '';
|
let TOKEN = '';
|
||||||
let currentReportId = '';
|
let currentReportId = '';
|
||||||
let ocrDataItems = [];
|
let ocrDataItems = [];
|
||||||
@@ -237,6 +238,7 @@ function showLoading(text='加载中...'){
|
|||||||
function hideLoading(){document.getElementById('globalLoading').style.display='none'}
|
function hideLoading(){document.getElementById('globalLoading').style.display='none'}
|
||||||
|
|
||||||
async function api(path,opts={}){
|
async function api(path,opts={}){
|
||||||
|
if(path.startsWith('/api/')) path = API_PREFIX + path.slice(4);
|
||||||
const headers = {'Authorization':'Bearer '+TOKEN, ...(opts.headers||{})};
|
const headers = {'Authorization':'Bearer '+TOKEN, ...(opts.headers||{})};
|
||||||
if(opts.body && !(opts.body instanceof FormData)){
|
if(opts.body && !(opts.body instanceof FormData)){
|
||||||
headers['Content-Type']='application/json';
|
headers['Content-Type']='application/json';
|
||||||
@@ -270,7 +272,7 @@ function badgeHtml(status){
|
|||||||
async function demoLogin(){
|
async function demoLogin(){
|
||||||
showLoading('正在登录...');
|
showLoading('正在登录...');
|
||||||
try{
|
try{
|
||||||
const data = await fetch(API+'/api/auth/demo-login',{method:'POST'}).then(r=>r.json());
|
const data = await api('/api/auth/demo-login',{method:'POST'});
|
||||||
TOKEN = data.access_token;
|
TOKEN = data.access_token;
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}catch(e){
|
}catch(e){
|
||||||
|
|||||||
3
frontend/.env.example
Normal file
3
frontend/.env.example
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
VITE_API_PREFIX=/app_03/api
|
||||||
|
VITE_UPLOADS_PREFIX=/uploads
|
||||||
|
|
||||||
@@ -65,6 +65,7 @@
|
|||||||
"vaul": "1.1.2"
|
"vaul": "1.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.0.0",
|
||||||
"@tailwindcss/vite": "4.1.12",
|
"@tailwindcss/vite": "4.1.12",
|
||||||
"@vitejs/plugin-basic-ssl": "^2.2.0",
|
"@vitejs/plugin-basic-ssl": "^2.2.0",
|
||||||
"@vitejs/plugin-react": "4.7.0",
|
"@vitejs/plugin-react": "4.7.0",
|
||||||
|
|||||||
@@ -1,5 +1,19 @@
|
|||||||
const TOKEN_KEY = "auth_token";
|
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 {
|
export function getToken(): string | null {
|
||||||
return localStorage.getItem(TOKEN_KEY);
|
return localStorage.getItem(TOKEN_KEY);
|
||||||
}
|
}
|
||||||
@@ -22,11 +36,11 @@ async function fetchApi<T>(path: string, options: RequestInit = {}): Promise<T>
|
|||||||
headers["Authorization"] = `Bearer ${token}`;
|
headers["Authorization"] = `Bearer ${token}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetch(path, { ...options, headers });
|
const res = await fetch(apiPath(path), { ...options, headers });
|
||||||
|
|
||||||
if (res.status === 401) {
|
if (res.status === 401) {
|
||||||
clearToken();
|
clearToken();
|
||||||
window.location.href = "/";
|
window.location.href = APP_BASE_URL;
|
||||||
throw new Error("登录已过期,请重新登录");
|
throw new Error("登录已过期,请重新登录");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,21 +62,21 @@ interface LoginResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function loginByPhone(phone: string, smsCode: string): Promise<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",
|
method: "POST",
|
||||||
body: JSON.stringify({ phone, sms_code: smsCode }),
|
body: JSON.stringify({ phone, sms_code: smsCode }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loginByWechat(code: string): Promise<LoginResponse> {
|
export async function loginByWechat(code: string): Promise<LoginResponse> {
|
||||||
return fetchApi<LoginResponse>("/api/auth/wechat-login", {
|
return fetchApi<LoginResponse>("/auth/wechat-login", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({ code }),
|
body: JSON.stringify({ code }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loginByDemo(): Promise<LoginResponse> {
|
export async function loginByDemo(): Promise<LoginResponse> {
|
||||||
return fetchApi<LoginResponse>("/api/auth/demo-login", {
|
return fetchApi<LoginResponse>("/auth/demo-login", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -91,7 +105,7 @@ export interface UserProfileDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getUserProfile(): Promise<UserProfileDTO> {
|
export async function getUserProfile(): Promise<UserProfileDTO> {
|
||||||
return fetchApi<UserProfileDTO>("/api/user/profile");
|
return fetchApi<UserProfileDTO>("/user/profile");
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OnboardingData {
|
export interface OnboardingData {
|
||||||
@@ -109,14 +123,14 @@ export interface OnboardingData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function submitOnboarding(data: OnboardingData): Promise<UserProfileDTO> {
|
export async function submitOnboarding(data: OnboardingData): Promise<UserProfileDTO> {
|
||||||
return fetchApi<UserProfileDTO>("/api/user/onboarding", {
|
return fetchApi<UserProfileDTO>("/user/onboarding", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateUserProfile(data: Record<string, unknown>): Promise<UserProfileDTO> {
|
export async function updateUserProfile(data: Record<string, unknown>): Promise<UserProfileDTO> {
|
||||||
return fetchApi<UserProfileDTO>("/api/user/profile", {
|
return fetchApi<UserProfileDTO>("/user/profile", {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
});
|
});
|
||||||
@@ -151,7 +165,7 @@ export async function uploadReportImage(file: File): Promise<UploadReportRespons
|
|||||||
const headers: Record<string, string> = {};
|
const headers: Record<string, string> = {};
|
||||||
if (token) headers["Authorization"] = `Bearer ${token}`;
|
if (token) headers["Authorization"] = `Bearer ${token}`;
|
||||||
|
|
||||||
const res = await fetch("/api/report/upload", {
|
const res = await fetch(apiPath("/report/upload"), {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers,
|
headers,
|
||||||
body: formData,
|
body: formData,
|
||||||
@@ -159,7 +173,7 @@ export async function uploadReportImage(file: File): Promise<UploadReportRespons
|
|||||||
|
|
||||||
if (res.status === 401) {
|
if (res.status === 401) {
|
||||||
clearToken();
|
clearToken();
|
||||||
window.location.href = "/";
|
window.location.href = APP_BASE_URL;
|
||||||
throw new Error("登录已过期,请重新登录");
|
throw new Error("登录已过期,请重新登录");
|
||||||
}
|
}
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
@@ -189,7 +203,7 @@ export async function addImageToReport(reportId: string, file: File): Promise<Ad
|
|||||||
const headers: Record<string, string> = {};
|
const headers: Record<string, string> = {};
|
||||||
if (token) headers["Authorization"] = `Bearer ${token}`;
|
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",
|
method: "POST",
|
||||||
headers,
|
headers,
|
||||||
body: formData,
|
body: formData,
|
||||||
@@ -197,7 +211,7 @@ export async function addImageToReport(reportId: string, file: File): Promise<Ad
|
|||||||
|
|
||||||
if (res.status === 401) {
|
if (res.status === 401) {
|
||||||
clearToken();
|
clearToken();
|
||||||
window.location.href = "/";
|
window.location.href = APP_BASE_URL;
|
||||||
throw new Error("登录已过期,请重新登录");
|
throw new Error("登录已过期,请重新登录");
|
||||||
}
|
}
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
@@ -237,7 +251,7 @@ export interface ReportResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function verifyReport(reportId: string, data: ReportVerifyRequest): Promise<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",
|
method: "PUT",
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
});
|
});
|
||||||
@@ -254,11 +268,11 @@ export interface ReportListItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getReportList(limit = 50): Promise<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> {
|
export async function getReport(reportId: string): Promise<ReportResponse> {
|
||||||
return fetchApi<ReportResponse>(`/api/report/${reportId}`);
|
return fetchApi<ReportResponse>(`/report/${reportId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Analysis ───
|
// ─── Analysis ───
|
||||||
@@ -297,13 +311,13 @@ export interface AnalysisResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function generateAnalysis(reportId: string): Promise<AnalysisResponse> {
|
export async function generateAnalysis(reportId: string): Promise<AnalysisResponse> {
|
||||||
return fetchApi<AnalysisResponse>(`/api/analysis/${reportId}/generate`, {
|
return fetchApi<AnalysisResponse>(`/analysis/${reportId}/generate`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAnalysis(reportId: string): Promise<AnalysisResponse> {
|
export async function getAnalysis(reportId: string): Promise<AnalysisResponse> {
|
||||||
return fetchApi<AnalysisResponse>(`/api/analysis/${reportId}`);
|
return fetchApi<AnalysisResponse>(`/analysis/${reportId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Chat ───
|
// ─── Chat ───
|
||||||
@@ -319,14 +333,14 @@ export interface ChatHistoryResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function sendChatMessage(reportId: string, content: string): Promise<ChatMessageResponse> {
|
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",
|
method: "POST",
|
||||||
body: JSON.stringify({ content }),
|
body: JSON.stringify({ content }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getChatHistory(reportId: string): Promise<ChatHistoryResponse> {
|
export async function getChatHistory(reportId: string): Promise<ChatHistoryResponse> {
|
||||||
return fetchApi<ChatHistoryResponse>(`/api/chat/${reportId}/history`);
|
return fetchApi<ChatHistoryResponse>(`/chat/${reportId}/history`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Health Records ───
|
// ─── Health Records ───
|
||||||
@@ -337,7 +351,7 @@ export interface HealthOverviewResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getHealthOverview(): Promise<HealthOverviewResponse> {
|
export async function getHealthOverview(): Promise<HealthOverviewResponse> {
|
||||||
return fetchApi<HealthOverviewResponse>("/api/health-records/overview");
|
return fetchApi<HealthOverviewResponse>("/health-records/overview");
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GrowthCurveResponse {
|
export interface GrowthCurveResponse {
|
||||||
@@ -345,7 +359,7 @@ export interface GrowthCurveResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getGrowthCurve(): Promise<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 {
|
export interface TimelineResponse {
|
||||||
@@ -361,7 +375,7 @@ export interface TimelineResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getTimeline(): Promise<TimelineResponse> {
|
export async function getTimeline(): Promise<TimelineResponse> {
|
||||||
return fetchApi<TimelineResponse>("/api/health-records/timeline");
|
return fetchApi<TimelineResponse>("/health-records/timeline");
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IndicatorTrendPoint {
|
export interface IndicatorTrendPoint {
|
||||||
@@ -385,7 +399,7 @@ export interface IndicatorTrendsResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getIndicatorTrends(): Promise<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 {
|
export interface ReportTypeTrendPoint {
|
||||||
@@ -414,7 +428,7 @@ export interface ReportTypeTrendsResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getReportTypeTrends(): Promise<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 ───
|
// ─── Calendar ───
|
||||||
@@ -437,11 +451,11 @@ export interface CheckupPlanResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getCheckupPlan(): Promise<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 }> {
|
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",
|
method: "PUT",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -454,7 +468,7 @@ export interface DoctorQuestionsResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getDoctorQuestions(reportId: string): Promise<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 {
|
export interface Doctor {
|
||||||
@@ -473,11 +487,11 @@ export interface Doctor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getExperts(): Promise<{ doctors: 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> {
|
export async function getExpertDetail(doctorId: string): Promise<Doctor> {
|
||||||
return fetchApi<Doctor>(`/api/doctor/experts/${doctorId}`);
|
return fetchApi<Doctor>(`/doctor/experts/${doctorId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Share ───
|
// ─── Share ───
|
||||||
@@ -503,7 +517,7 @@ export async function generateShareData(
|
|||||||
reportId: string,
|
reportId: string,
|
||||||
options: { privacy_mode?: boolean; include_details?: boolean } = {}
|
options: { privacy_mode?: boolean; include_details?: boolean } = {}
|
||||||
): Promise<ShareDataResponse> {
|
): Promise<ShareDataResponse> {
|
||||||
return fetchApi<ShareDataResponse>(`/api/share/${reportId}/generate`, {
|
return fetchApi<ShareDataResponse>(`/share/${reportId}/generate`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({ privacy_mode: true, include_details: false, ...options }),
|
body: JSON.stringify({ privacy_mode: true, include_details: false, ...options }),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,40 +1,46 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig, loadEnv } from 'vite'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import tailwindcss from '@tailwindcss/vite'
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
import basicSsl from '@vitejs/plugin-basic-ssl'
|
import basicSsl from '@vitejs/plugin-basic-ssl'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig(({ mode }) => {
|
||||||
base: '/app_03/',
|
const env = loadEnv(mode, process.cwd(), '')
|
||||||
plugins: [
|
const apiPrefix = env.VITE_API_PREFIX || '/api'
|
||||||
// The React and Tailwind plugins are both required for Make, even if
|
const uploadsPrefix = env.VITE_UPLOADS_PREFIX || '/uploads'
|
||||||
// Tailwind is not being actively used – do not remove them
|
|
||||||
react(),
|
|
||||||
tailwindcss(),
|
|
||||||
// basicSsl(), // disabled for cloudflare tunnel compatibility
|
|
||||||
],
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
// Alias @ to the src directory
|
|
||||||
'@': path.resolve(__dirname, './src'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// File types to support raw imports. Never add .css, .tsx, or .ts files to this.
|
return {
|
||||||
assetsInclude: ['**/*.svg', '**/*.csv'],
|
base: '/app_03/',
|
||||||
|
plugins: [
|
||||||
server: {
|
// The React and Tailwind plugins are both required for Make, even if
|
||||||
host: '0.0.0.0',
|
// Tailwind is not being actively used – do not remove them
|
||||||
allowedHosts: ['.trycloudflare.com', '.loca.lt'],
|
react(),
|
||||||
proxy: {
|
tailwindcss(),
|
||||||
'/api': {
|
// basicSsl(), // disabled for cloudflare tunnel compatibility
|
||||||
target: 'http://localhost:8000',
|
],
|
||||||
changeOrigin: true,
|
resolve: {
|
||||||
},
|
alias: {
|
||||||
'/uploads': {
|
// Alias @ to the src directory
|
||||||
target: 'http://localhost:8000',
|
'@': path.resolve(__dirname, './src'),
|
||||||
changeOrigin: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
|
// File types to support raw imports. Never add .css, .tsx, or .ts files to this.
|
||||||
|
assetsInclude: ['**/*.svg', '**/*.csv'],
|
||||||
|
|
||||||
|
server: {
|
||||||
|
host: '0.0.0.0',
|
||||||
|
allowedHosts: ['.trycloudflare.com', '.loca.lt'],
|
||||||
|
proxy: {
|
||||||
|
[apiPrefix]: {
|
||||||
|
target: 'http://localhost:8000',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
[uploadsPrefix]: {
|
||||||
|
target: 'http://localhost:8000',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user