import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router"; import { useAuthStore } from "../store/auth"; import { useStudyStore } from "../store/study"; import Layout from "../components/Layout.vue"; import Login from "../views/Login.vue"; import Register from "../views/Register.vue"; import ForgotPassword from "../views/ForgotPassword.vue"; import StudyHome from "../views/StudyHome.vue"; import MyWorkbench from "../views/workbench/MyWorkbench.vue"; import FaqDetail from "../views/FaqDetail.vue"; import AuditLogs from "../views/admin/AuditLogs.vue"; import AdminUsers from "../views/admin/Users.vue"; import AdminUserApproval from "../views/admin/AdminUserApproval.vue"; import AdminProjects from "../views/admin/Projects.vue"; import ProjectMembers from "../views/admin/ProjectMembers.vue"; import AdminSites from "../views/admin/Sites.vue"; import ProjectDetail from "../views/admin/ProjectDetail.vue"; import ProfileSettings from "../views/ProfileSettings.vue"; import ProjectOverview from "../views/ia/ProjectOverview.vue"; import FinanceContracts from "../views/ia/FinanceContracts.vue"; import FinanceSpecial from "../views/ia/FinanceSpecial.vue"; import DrugShipments from "../views/ia/DrugShipments.vue"; import StartupFeasibilityEthics from "../views/ia/StartupFeasibilityEthics.vue"; import StartupMeetingAuth from "../views/ia/StartupMeetingAuth.vue"; import SubjectManagement from "../views/ia/SubjectManagement.vue"; import MonitoringPlaceholder from "../views/ia/MonitoringPlaceholder.vue"; import AuditPlaceholder from "../views/ia/AuditPlaceholder.vue"; import KnowledgeMedicalConsult from "../views/ia/KnowledgeMedicalConsult.vue"; import KnowledgeNotes from "../views/ia/KnowledgeNotes.vue"; import ContractForm from "../views/finance/ContractForm.vue"; import ContractDetail from "../views/finance/ContractDetail.vue"; import SpecialForm from "../views/finance/SpecialForm.vue"; import SpecialDetail from "../views/finance/SpecialDetail.vue"; import ShipmentForm from "../views/drug/ShipmentForm.vue"; import ShipmentDetail from "../views/drug/ShipmentDetail.vue"; import FeasibilityForm from "../views/startup/FeasibilityForm.vue"; import FeasibilityDetail from "../views/startup/FeasibilityDetail.vue"; import EthicsForm from "../views/startup/EthicsForm.vue"; import EthicsDetail from "../views/startup/EthicsDetail.vue"; import KickoffForm from "../views/startup/KickoffForm.vue"; import KickoffDetail from "../views/startup/KickoffDetail.vue"; import TrainingForm from "../views/startup/TrainingForm.vue"; import TrainingDetail from "../views/startup/TrainingDetail.vue"; import NoteForm from "../views/knowledge/NoteForm.vue"; import NoteDetail from "../views/knowledge/NoteDetail.vue"; import SubjectForm from "../views/subjects/SubjectForm.vue"; import SubjectDetail from "../views/subjects/SubjectDetail.vue"; import { TEXT } from "../locales"; const routes: RouteRecordRaw[] = [ { path: "/login", name: "Login", component: Login, meta: { public: true, title: TEXT.common.actions.login }, }, { path: "/register", name: "Register", component: Register, meta: { public: true, title: TEXT.common.actions.register }, }, { path: "/forgot-password", name: "ForgotPassword", component: ForgotPassword, meta: { public: true, title: TEXT.modules.auth.forgotTitle }, }, { path: "/", component: Layout, redirect: "/workbench", children: [ { path: "workbench", name: "MyWorkbench", component: MyWorkbench, meta: { title: TEXT.menu.workbench }, }, { path: "profile", name: "ProfileSettings", component: ProfileSettings, meta: { title: TEXT.menu.profile }, }, { path: "project/overview", name: "ProjectOverview", component: ProjectOverview, meta: { title: TEXT.menu.projectOverview, requiresStudy: true }, }, { path: "finance/contracts", name: "FinanceContracts", component: FinanceContracts, meta: { title: TEXT.menu.financeContracts, requiresStudy: true }, }, { path: "finance/contracts/new", name: "FinanceContractNew", component: ContractForm, meta: { title: TEXT.common.actions.add + TEXT.menu.financeContracts, requiresStudy: true }, }, { path: "finance/contracts/:contractId", name: "FinanceContractDetail", component: ContractDetail, meta: { title: TEXT.modules.financeContracts.detailTitle, requiresStudy: true }, }, { path: "finance/contracts/:contractId/edit", name: "FinanceContractEdit", component: ContractForm, meta: { title: TEXT.common.actions.edit + TEXT.menu.financeContracts, requiresStudy: true }, }, { path: "finance/special", name: "FinanceSpecial", component: FinanceSpecial, meta: { title: TEXT.menu.financeSpecials, requiresStudy: true }, }, { path: "finance/special/new", name: "FinanceSpecialNew", component: SpecialForm, meta: { title: TEXT.common.actions.add + TEXT.menu.financeSpecials, requiresStudy: true }, }, { path: "finance/special/:specialId", name: "FinanceSpecialDetail", component: SpecialDetail, meta: { title: TEXT.modules.financeSpecials.detailTitle, requiresStudy: true }, }, { path: "finance/special/:specialId/edit", name: "FinanceSpecialEdit", component: SpecialForm, meta: { title: TEXT.common.actions.edit + TEXT.menu.financeSpecials, requiresStudy: true }, }, { path: "drug/shipments", name: "DrugShipments", component: DrugShipments, meta: { title: TEXT.menu.drugShipments, requiresStudy: true }, }, { path: "drug/shipments/new", name: "DrugShipmentNew", component: ShipmentForm, meta: { title: TEXT.common.actions.add + TEXT.menu.drugShipments, requiresStudy: true }, }, { path: "drug/shipments/:shipmentId", name: "DrugShipmentDetail", component: ShipmentDetail, meta: { title: TEXT.modules.drugShipments.detailTitle, requiresStudy: true }, }, { path: "drug/shipments/:shipmentId/edit", name: "DrugShipmentEdit", component: ShipmentForm, meta: { title: TEXT.common.actions.edit + TEXT.menu.drugShipments, requiresStudy: true }, }, { path: "startup/feasibility-ethics", name: "StartupFeasibilityEthics", component: StartupFeasibilityEthics, meta: { title: TEXT.menu.startupFeasibilityEthics, requiresStudy: true }, }, { path: "startup/feasibility/new", name: "StartupFeasibilityNew", component: FeasibilityForm, meta: { title: TEXT.common.actions.add + TEXT.modules.startupFeasibilityEthics.feasibilityTab, requiresStudy: true }, }, { path: "startup/feasibility/:recordId", name: "StartupFeasibilityDetail", component: FeasibilityDetail, meta: { title: TEXT.modules.startupFeasibilityEthics.feasibilityDetailTitle, requiresStudy: true }, }, { path: "startup/feasibility/:recordId/edit", name: "StartupFeasibilityEdit", component: FeasibilityForm, meta: { title: TEXT.common.actions.edit + TEXT.modules.startupFeasibilityEthics.feasibilityTab, requiresStudy: true }, }, { path: "startup/ethics/new", name: "StartupEthicsNew", component: EthicsForm, meta: { title: TEXT.common.actions.add + TEXT.modules.startupFeasibilityEthics.ethicsTab, requiresStudy: true }, }, { path: "startup/ethics/:recordId", name: "StartupEthicsDetail", component: EthicsDetail, meta: { title: TEXT.modules.startupFeasibilityEthics.ethicsDetailTitle, requiresStudy: true }, }, { path: "startup/ethics/:recordId/edit", name: "StartupEthicsEdit", component: EthicsForm, meta: { title: TEXT.common.actions.edit + TEXT.modules.startupFeasibilityEthics.ethicsTab, requiresStudy: true }, }, { path: "startup/meeting-auth", name: "StartupMeetingAuth", component: StartupMeetingAuth, meta: { title: TEXT.menu.startupMeetingAuth, requiresStudy: true }, }, { path: "startup/kickoff/new", name: "StartupKickoffNew", component: KickoffForm, meta: { title: TEXT.common.actions.add + TEXT.modules.startupMeetingAuth.kickoffTab, requiresStudy: true }, }, { path: "startup/kickoff/:meetingId", name: "StartupKickoffDetail", component: KickoffDetail, meta: { title: TEXT.modules.startupMeetingAuth.kickoffDetailTitle, requiresStudy: true }, }, { path: "startup/kickoff/:meetingId/edit", name: "StartupKickoffEdit", component: KickoffForm, meta: { title: TEXT.common.actions.edit + TEXT.modules.startupMeetingAuth.kickoffTab, requiresStudy: true }, }, { path: "startup/training/new", name: "StartupTrainingNew", component: TrainingForm, meta: { title: TEXT.common.actions.add + TEXT.common.fields.person, requiresStudy: true }, }, { path: "startup/training/:recordId", name: "StartupTrainingDetail", component: TrainingDetail, meta: { title: TEXT.modules.startupMeetingAuth.trainingDetailTitle, requiresStudy: true }, }, { path: "startup/training/:recordId/edit", name: "StartupTrainingEdit", component: TrainingForm, meta: { title: TEXT.common.actions.edit + TEXT.modules.startupMeetingAuth.trainingTab, requiresStudy: true }, }, { path: "subjects", name: "SubjectManagement", component: SubjectManagement, meta: { title: TEXT.menu.subjects, requiresStudy: true }, }, { path: "subjects/new", name: "SubjectNew", component: SubjectForm, meta: { title: TEXT.common.actions.add + TEXT.menu.subjects, requiresStudy: true }, }, { path: "subjects/:subjectId", name: "SubjectDetail", component: SubjectDetail, meta: { title: TEXT.modules.subjectDetail.title, requiresStudy: true }, }, { path: "subjects/:subjectId/edit", name: "SubjectEdit", component: SubjectForm, meta: { title: TEXT.common.actions.edit + TEXT.menu.subjects, requiresStudy: true }, }, { path: "monitoring", name: "MonitoringPlaceholder", component: MonitoringPlaceholder, meta: { title: TEXT.menu.monitoring, requiresStudy: true }, }, { path: "audit", name: "AuditPlaceholder", component: AuditPlaceholder, meta: { title: TEXT.menu.audit, requiresStudy: true }, }, { path: "knowledge/medical-consult", name: "KnowledgeMedicalConsult", component: KnowledgeMedicalConsult, meta: { title: TEXT.menu.knowledgeMedicalConsult, requiresStudy: true }, }, { path: "knowledge/medical-consult/:itemId", name: "KnowledgeMedicalConsultDetail", component: FaqDetail, meta: { title: TEXT.modules.knowledgeMedicalConsult.detailTitle, requiresStudy: true }, }, { path: "knowledge/notes", name: "KnowledgeNotes", component: KnowledgeNotes, meta: { title: TEXT.menu.knowledgeNotes, requiresStudy: true }, }, { path: "knowledge/notes/new", name: "KnowledgeNoteNew", component: NoteForm, meta: { title: TEXT.common.actions.add + TEXT.menu.knowledgeNotes, requiresStudy: true }, }, { path: "knowledge/notes/:noteId", name: "KnowledgeNoteDetail", component: NoteDetail, meta: { title: TEXT.modules.knowledgeNotes.detailTitle, requiresStudy: true }, }, { path: "knowledge/notes/:noteId/edit", name: "KnowledgeNoteEdit", component: NoteForm, meta: { title: TEXT.common.actions.edit + TEXT.menu.knowledgeNotes, requiresStudy: true }, }, ], }, { path: "/admin", component: Layout, redirect: "/admin/users", children: [ { path: "users", name: "AdminUsers", component: AdminUsers, meta: { title: TEXT.menu.accountManagement, requiresAdmin: true }, }, { path: "user-approval", name: "AdminUserApproval", component: AdminUserApproval, meta: { title: TEXT.modules.adminUserApproval.title, requiresAdmin: true }, }, { path: "projects", name: "AdminProjects", component: AdminProjects, meta: { title: TEXT.menu.projectManagement, requiresAdmin: true }, }, { path: "projects/:projectId/members", name: "AdminProjectMembers", component: ProjectMembers, meta: { title: TEXT.modules.adminProjectMembers.memberLabel, requiresAdmin: true }, }, { path: "projects/:projectId/sites", name: "AdminSites", component: AdminSites, meta: { title: TEXT.modules.adminSites.title, requiresAdmin: true }, }, { path: "audit-logs", name: "AdminAuditLogs", component: AuditLogs, meta: { title: TEXT.menu.auditLogs, requiresAdmin: true }, }, ], }, { path: "/projects/:projectId", name: "ProjectDetail", component: ProjectDetail, meta: { title: TEXT.modules.adminProjects.detailTitle, requiresAdmin: true }, }, ]; const router = createRouter({ history: createWebHistory(), routes, }); router.beforeEach(async (to, _from, next) => { const auth = useAuthStore(); const studyStore = useStudyStore(); const getToken = () => auth.token; if (!auth.user && getToken()) { try { await auth.fetchMe(); } catch { // token 失效或用户不可用时立即登出,避免重复 401 auth.logout(); } } const token = getToken(); if (token && !studyStore.currentStudy) { await studyStore.ensureDefaultStudy(); } if (!to.meta.public && !token) { next({ path: "/login" }); return; } if ((to.path === "/login" || to.path === "/register") && token) { if (!auth.forceLogin) { next({ path: studyStore.currentStudy ? "/project/overview" : "/workbench" }); return; } } if (to.meta.requiresAdmin && auth.user?.role !== "ADMIN") { next({ path: "/workbench" }); return; } if (to.name === "AuditLogs") { const role = auth.user?.role; const studyRole = studyStore.currentStudyRole; if (!(role === "ADMIN" || role === "PM" || studyRole === "PM")) { next({ path: "/project/overview" }); return; } } if (to.meta.requiresStudy && !studyStore.currentStudy) { next({ path: "/workbench" }); return; } if (to.path === "/workbench" && studyStore.currentStudy) { next({ path: "/project/overview" }); return; } next(); }); export default router;