diff --git a/frontend/src/api/axios.ts b/frontend/src/api/axios.ts index d4a99937..5cf7fe7a 100644 --- a/frontend/src/api/axios.ts +++ b/frontend/src/api/axios.ts @@ -3,6 +3,7 @@ import { ElMessage } from "element-plus"; import router from "../router"; import { getToken, clearToken } from "../utils/auth"; import type { ApiError } from "../types/api"; +import { useStudyStore } from "../store/study"; const instance: AxiosInstance = axios.create({ baseURL: "/", @@ -23,6 +24,16 @@ instance.interceptors.response.use( async (error: AxiosError) => { const status = error.response?.status; const data = error.response?.data; + // 如果当前项目不存在或已失效,清理项目并跳到项目列表 + if (status === 404 && typeof data?.message === "string" && data.message.toLowerCase().includes("study")) { + try { + const studyStore = useStudyStore(); + studyStore.clearCurrentStudy(); + } catch { + /* ignore */ + } + router.push("/studies"); + } if (status === 401) { clearToken(); ElMessage.error(data?.message || "登录已过期,请重新登录"); diff --git a/frontend/src/api/impBatches.ts b/frontend/src/api/impBatches.ts new file mode 100644 index 00000000..3e7034cc --- /dev/null +++ b/frontend/src/api/impBatches.ts @@ -0,0 +1,34 @@ +import { apiGet, apiPatch, apiPost } from "./axios"; +import type { AxiosResponse } from "axios"; +import type { ApiListResponse } from "../types/api"; + +export interface ImpBatch { + id: string; + study_id: string; + product_id: string; + batch_no: string; + expiry_date?: string | null; + manufacture_date?: string | null; + status: string; + created_at: string; + updated_at: string; +} + +export const fetchImpBatches = ( + studyId: string, + params?: Record +): Promise>> => + apiGet(`/api/v1/studies/${studyId}/imp/batches/`, { params }); + +export const createImpBatch = ( + studyId: string, + payload: Record +): Promise> => + apiPost(`/api/v1/studies/${studyId}/imp/batches/`, payload); + +export const updateImpBatch = ( + studyId: string, + batchId: string, + payload: Record +): Promise> => + apiPatch(`/api/v1/studies/${studyId}/imp/batches/${batchId}`, payload); diff --git a/frontend/src/api/impInventory.ts b/frontend/src/api/impInventory.ts new file mode 100644 index 00000000..1b3a7f85 --- /dev/null +++ b/frontend/src/api/impInventory.ts @@ -0,0 +1,16 @@ +import { apiGet } from "./axios"; +import type { AxiosResponse } from "axios"; +import type { ApiListResponse } from "../types/api"; + +export interface ImpInventoryRow { + site_id: string; + batch_id: string; + quantity_on_hand: number; + updated_at: string; +} + +export const fetchImpInventory = ( + studyId: string, + params?: Record +): Promise>> => + apiGet(`/api/v1/studies/${studyId}/imp/inventory/`, { params }); diff --git a/frontend/src/api/impProducts.ts b/frontend/src/api/impProducts.ts new file mode 100644 index 00000000..c5e6b1f5 --- /dev/null +++ b/frontend/src/api/impProducts.ts @@ -0,0 +1,34 @@ +import { apiGet, apiPatch, apiPost } from "./axios"; +import type { AxiosResponse } from "axios"; +import type { ApiListResponse } from "../types/api"; + +export interface ImpProduct { + id: string; + study_id: string; + name: string; + form?: string | null; + strength?: string | null; + unit: string; + description?: string | null; + is_active: boolean; + created_at: string; + updated_at: string; +} + +export const fetchImpProducts = ( + studyId: string +): Promise>> => + apiGet(`/api/v1/studies/${studyId}/imp/products/`); + +export const createImpProduct = ( + studyId: string, + payload: Record +): Promise> => + apiPost(`/api/v1/studies/${studyId}/imp/products/`, payload); + +export const updateImpProduct = ( + studyId: string, + productId: string, + payload: Record +): Promise> => + apiPatch(`/api/v1/studies/${studyId}/imp/products/${productId}`, payload); diff --git a/frontend/src/api/impTransactions.ts b/frontend/src/api/impTransactions.ts new file mode 100644 index 00000000..af351c28 --- /dev/null +++ b/frontend/src/api/impTransactions.ts @@ -0,0 +1,30 @@ +import { apiGet, apiPost } from "./axios"; +import type { AxiosResponse } from "axios"; +import type { ApiListResponse } from "../types/api"; + +export interface ImpTransaction { + id: string; + study_id: string; + site_id: string; + batch_id: string; + subject_id?: string | null; + tx_type: string; + quantity: number; + tx_date: string; + reference?: string | null; + notes?: string | null; + created_by: string; + created_at: string; +} + +export const fetchImpTransactions = ( + studyId: string, + params?: Record +): Promise>> => + apiGet(`/api/v1/studies/${studyId}/imp/transactions/`, { params }); + +export const createImpTransaction = ( + studyId: string, + payload: Record +): Promise> => + apiPost(`/api/v1/studies/${studyId}/imp/transactions/`, payload); diff --git a/frontend/src/components/ImpBatchForm.vue b/frontend/src/components/ImpBatchForm.vue new file mode 100644 index 00000000..988263fc --- /dev/null +++ b/frontend/src/components/ImpBatchForm.vue @@ -0,0 +1,118 @@ + + + diff --git a/frontend/src/components/ImpProductForm.vue b/frontend/src/components/ImpProductForm.vue new file mode 100644 index 00000000..4532631c --- /dev/null +++ b/frontend/src/components/ImpProductForm.vue @@ -0,0 +1,106 @@ + + + diff --git a/frontend/src/components/ImpTransactionForm.vue b/frontend/src/components/ImpTransactionForm.vue new file mode 100644 index 00000000..612be9bd --- /dev/null +++ b/frontend/src/components/ImpTransactionForm.vue @@ -0,0 +1,104 @@ + + + diff --git a/frontend/src/components/Layout.vue b/frontend/src/components/Layout.vue index f01a9940..66ff43b6 100644 --- a/frontend/src/components/Layout.vue +++ b/frontend/src/components/Layout.vue @@ -24,6 +24,15 @@ 风险/问题 数据问题 SDV/SDR + + + 库存 + 产品 + 批次 + 交易台账 + + 费用 + FAQ @@ -46,7 +55,7 @@ const router = useRouter(); const onLogout = () => { auth.logout(); study.clearCurrentStudy(); - router.push("/login"); + router.replace("/login"); }; diff --git a/frontend/src/components/QuickActions.vue b/frontend/src/components/QuickActions.vue index c6080ce8..1f33d178 100644 --- a/frontend/src/components/QuickActions.vue +++ b/frontend/src/components/QuickActions.vue @@ -18,7 +18,7 @@ const actions = [ { label: "受试者", path: "/study/subjects" }, { label: "AE", path: "/study/aes" }, { label: "数据问题", path: "/study/data-queries" }, - { label: "药品", path: "/study/imp" }, + { label: "药品", path: "/study/imp/inventory" }, { label: "费用", path: "/study/finance" }, { label: "FAQ", path: "/study/faq" }, ]; diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index d5973f69..47f7dbe9 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -15,6 +15,10 @@ import AeDetail from "../views/AeDetail.vue"; import DataQueries from "../views/DataQueries.vue"; import DataQueryDetail from "../views/DataQueryDetail.vue"; import Imp from "../views/Imp.vue"; +import ImpProducts from "../views/ImpProducts.vue"; +import ImpBatches from "../views/ImpBatches.vue"; +import ImpInventory from "../views/ImpInventory.vue"; +import ImpTransactions from "../views/ImpTransactions.vue"; import Finance from "../views/Finance.vue"; import Faq from "../views/Faq.vue"; import Issues from "../views/Issues.vue"; @@ -111,6 +115,30 @@ const routes: RouteRecordRaw[] = [ component: Imp, meta: { title: "药品", requiresStudy: true }, }, + { + path: "study/imp/products", + name: "StudyImpProducts", + component: ImpProducts, + meta: { title: "IMP 产品", requiresStudy: true }, + }, + { + path: "study/imp/batches", + name: "StudyImpBatches", + component: ImpBatches, + meta: { title: "IMP 批次", requiresStudy: true }, + }, + { + path: "study/imp/inventory", + name: "StudyImpInventory", + component: ImpInventory, + meta: { title: "IMP 库存", requiresStudy: true }, + }, + { + path: "study/imp/transactions", + name: "StudyImpTransactions", + component: ImpTransactions, + meta: { title: "IMP 交易", requiresStudy: true }, + }, { path: "study/finance", name: "StudyFinance", diff --git a/frontend/src/views/Imp.vue b/frontend/src/views/Imp.vue index d68ba290..df19c832 100644 --- a/frontend/src/views/Imp.vue +++ b/frontend/src/views/Imp.vue @@ -1,13 +1,31 @@ - + diff --git a/frontend/src/views/ImpBatches.vue b/frontend/src/views/ImpBatches.vue new file mode 100644 index 00000000..995290ff --- /dev/null +++ b/frontend/src/views/ImpBatches.vue @@ -0,0 +1,153 @@ + + + + + diff --git a/frontend/src/views/ImpInventory.vue b/frontend/src/views/ImpInventory.vue new file mode 100644 index 00000000..3647504a --- /dev/null +++ b/frontend/src/views/ImpInventory.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/frontend/src/views/ImpProducts.vue b/frontend/src/views/ImpProducts.vue new file mode 100644 index 00000000..ecc1dc69 --- /dev/null +++ b/frontend/src/views/ImpProducts.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/frontend/src/views/ImpTransactions.vue b/frontend/src/views/ImpTransactions.vue new file mode 100644 index 00000000..53760bfd --- /dev/null +++ b/frontend/src/views/ImpTransactions.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/pg_data/base/16384/24576 b/pg_data/base/16384/24576 index b10fc06b..e2490e63 100644 Binary files a/pg_data/base/16384/24576 and b/pg_data/base/16384/24576 differ diff --git a/pg_data/base/16384/24584 b/pg_data/base/16384/24584 index 19b10bb9..27978392 100644 Binary files a/pg_data/base/16384/24584 and b/pg_data/base/16384/24584 differ diff --git a/pg_data/base/16384/24618 b/pg_data/base/16384/24618 index 35b0dc3b..98f31082 100644 Binary files a/pg_data/base/16384/24618 and b/pg_data/base/16384/24618 differ diff --git a/pg_data/base/16384/24653 b/pg_data/base/16384/24653 index 8b0a9b26..66ddbeb0 100644 Binary files a/pg_data/base/16384/24653 and b/pg_data/base/16384/24653 differ diff --git a/pg_data/base/16384/24659 b/pg_data/base/16384/24659 index 67429e9d..1071a1a8 100644 Binary files a/pg_data/base/16384/24659 and b/pg_data/base/16384/24659 differ diff --git a/pg_data/base/16384/24710 b/pg_data/base/16384/24710 index 55c48b40..0f521436 100644 Binary files a/pg_data/base/16384/24710 and b/pg_data/base/16384/24710 differ diff --git a/pg_data/base/16384/24717 b/pg_data/base/16384/24717 index 46ac7c0e..16921272 100644 Binary files a/pg_data/base/16384/24717 and b/pg_data/base/16384/24717 differ diff --git a/pg_data/base/16384/24719 b/pg_data/base/16384/24719 index fef3565c..c81848b7 100644 Binary files a/pg_data/base/16384/24719 and b/pg_data/base/16384/24719 differ diff --git a/pg_data/base/16384/24726 b/pg_data/base/16384/24726 index 68de7746..e54969e4 100644 Binary files a/pg_data/base/16384/24726 and b/pg_data/base/16384/24726 differ diff --git a/pg_data/base/16384/24774 b/pg_data/base/16384/24774 index d63def58..26b1bb49 100644 Binary files a/pg_data/base/16384/24774 and b/pg_data/base/16384/24774 differ diff --git a/pg_data/base/16384/24797 b/pg_data/base/16384/24797 index 6565420e..9c7e93f8 100644 Binary files a/pg_data/base/16384/24797 and b/pg_data/base/16384/24797 differ diff --git a/pg_data/base/16384/24802 b/pg_data/base/16384/24802 index e4e3c179..6ca74a51 100644 Binary files a/pg_data/base/16384/24802 and b/pg_data/base/16384/24802 differ diff --git a/pg_data/base/16384/24804 b/pg_data/base/16384/24804 index 0cb790db..9c6ed6cc 100644 Binary files a/pg_data/base/16384/24804 and b/pg_data/base/16384/24804 differ diff --git a/pg_data/base/16384/24816 b/pg_data/base/16384/24816 index 704bd8e0..695df659 100644 Binary files a/pg_data/base/16384/24816 and b/pg_data/base/16384/24816 differ diff --git a/pg_data/base/16384/24817 b/pg_data/base/16384/24817 index 31c2b732..86f43d9c 100644 Binary files a/pg_data/base/16384/24817 and b/pg_data/base/16384/24817 differ diff --git a/pg_data/base/16384/24844 b/pg_data/base/16384/24844 index 0037f3df..16cd3703 100644 Binary files a/pg_data/base/16384/24844 and b/pg_data/base/16384/24844 differ diff --git a/pg_data/base/16384/25004 b/pg_data/base/16384/25004 index 8bdbece0..bde9d34b 100644 Binary files a/pg_data/base/16384/25004 and b/pg_data/base/16384/25004 differ diff --git a/pg_data/base/16384/25008 b/pg_data/base/16384/25008 index 14745285..f9f1bfe4 100644 Binary files a/pg_data/base/16384/25008 and b/pg_data/base/16384/25008 differ diff --git a/pg_data/base/16384/25010 b/pg_data/base/16384/25010 index 309b508a..23448582 100644 Binary files a/pg_data/base/16384/25010 and b/pg_data/base/16384/25010 differ diff --git a/pg_data/base/16384/25027 b/pg_data/base/16384/25027 index a49e7ee5..d8efc8d0 100644 Binary files a/pg_data/base/16384/25027 and b/pg_data/base/16384/25027 differ diff --git a/pg_data/base/16384/25028 b/pg_data/base/16384/25028 index 3fc65472..768c0829 100644 Binary files a/pg_data/base/16384/25028 and b/pg_data/base/16384/25028 differ diff --git a/pg_data/base/16384/25029 b/pg_data/base/16384/25029 index cffa0d94..5f755e8f 100644 Binary files a/pg_data/base/16384/25029 and b/pg_data/base/16384/25029 differ diff --git a/pg_data/base/16384/25030 b/pg_data/base/16384/25030 index adaf9de7..075cd507 100644 Binary files a/pg_data/base/16384/25030 and b/pg_data/base/16384/25030 differ diff --git a/pg_data/base/16384/25036 b/pg_data/base/16384/25036 index 3535da67..32d92379 100644 Binary files a/pg_data/base/16384/25036 and b/pg_data/base/16384/25036 differ diff --git a/pg_data/base/16384/25063 b/pg_data/base/16384/25063 index 55a6d3bf..54236cc4 100644 Binary files a/pg_data/base/16384/25063 and b/pg_data/base/16384/25063 differ diff --git a/pg_data/base/16384/25064 b/pg_data/base/16384/25064 index d851e10c..b4942dc4 100644 Binary files a/pg_data/base/16384/25064 and b/pg_data/base/16384/25064 differ diff --git a/pg_data/base/16384/25065 b/pg_data/base/16384/25065 index 46605356..a9600ef7 100644 Binary files a/pg_data/base/16384/25065 and b/pg_data/base/16384/25065 differ diff --git a/pg_data/base/16384/2604 b/pg_data/base/16384/2604 index cfdcff94..1456965e 100644 Binary files a/pg_data/base/16384/2604 and b/pg_data/base/16384/2604 differ diff --git a/pg_data/base/16384/2619 b/pg_data/base/16384/2619 index 88b7bb7f..b6961db9 100644 Binary files a/pg_data/base/16384/2619 and b/pg_data/base/16384/2619 differ diff --git a/pg_data/global/pg_control b/pg_data/global/pg_control index 1d98f731..7dbaa0a8 100644 Binary files a/pg_data/global/pg_control and b/pg_data/global/pg_control differ diff --git a/pg_data/pg_subtrans/0000 b/pg_data/pg_subtrans/0000 index 459cd5d8..86e2f0bf 100644 Binary files a/pg_data/pg_subtrans/0000 and b/pg_data/pg_subtrans/0000 differ diff --git a/pg_data/pg_wal/000000010000000000000001 b/pg_data/pg_wal/000000010000000000000001 index 413d8162..cb1d0555 100644 Binary files a/pg_data/pg_wal/000000010000000000000001 and b/pg_data/pg_wal/000000010000000000000001 differ diff --git a/pg_data/pg_xact/0000 b/pg_data/pg_xact/0000 index e37dc51c..3a30c261 100644 Binary files a/pg_data/pg_xact/0000 and b/pg_data/pg_xact/0000 differ