信息架构/菜单框架大重构-20260109

This commit is contained in:
Cheng Zhou
2026-01-09 11:19:04 +08:00
parent ba3cf95b8a
commit 9021c7fe2b
188 changed files with 7632 additions and 11658 deletions
+24 -43
View File
@@ -59,7 +59,7 @@
<div class="quick-entry-card">
<div class="entry-header">
<span class="entry-title">快捷入口</span>
<el-button type="primary" link @click="goMore('/study/home')" class="entry-more">
<el-button type="primary" link @click="goMore('/project/overview')" class="entry-more">
进入项目 <el-icon class="el-icon--right"><Right /></el-icon>
</el-button>
</div>
@@ -96,9 +96,8 @@ import { ElMessage } from "element-plus";
import { useAuthStore } from "../../store/auth";
import { useStudyStore } from "../../store/study";
import { fetchAes } from "../../api/aes";
import { fetchFinanceItems } from "../../api/finance";
import { fetchImpTransactions } from "../../api/impTransactions";
import { Right, Timer, UserFilled, Warning, Money, Collection, Management } from "@element-plus/icons-vue";
import { listFinanceContracts } from "../../api/financeContracts";
import { Right, Timer, UserFilled, Warning, Money, Collection } from "@element-plus/icons-vue";
interface WorkItem {
title: string;
@@ -132,26 +131,24 @@ const quickActions = computed(() => {
if (!currentStudyId.value) return [];
if (role.value === "CRA")
return [
{ label: "伦理启动", path: "/study/milestones", icon: Timer },
{ label: "AE 列表", path: "/study/aes", icon: Warning },
{ label: "立项与伦理", path: "/startup/feasibility-ethics", icon: Timer },
{ label: "AE 列表", path: "/subjects", icon: Warning },
];
if (role.value === "PM")
return [
{ label: "伦理启动", path: "/study/milestones", icon: Timer },
{ label: "费用审核", path: "/study/finance", icon: Money },
{ label: "公告管理", path: "/study/faq", icon: Collection },
{ label: "立项与伦理", path: "/startup/feasibility-ethics", icon: Timer },
{ label: "合同费用", path: "/finance/contracts", icon: Money },
{ label: "医学咨询", path: "/knowledge/medical-consult", icon: Collection },
];
if (role.value === "PV") return [{ label: "AE 列表", path: "/study/aes", icon: Warning }];
if (role.value === "IMP") return [{ label: "药品台账", path: "/study/imp/transactions", icon: Management }];
return [{ label: "伦理启动", path: "/study/milestones", icon: Timer }];
if (role.value === "PV") return [{ label: "AE 列表", path: "/subjects", icon: Warning }];
return [{ label: "立项与伦理", path: "/startup/feasibility-ethics", icon: Timer }];
});
const todayMorePath = computed(() => (role.value === "IMP" ? "/study/imp/transactions" : "/study/aes"));
const overdueMorePath = computed(() => (role.value === "IMP" ? "/study/imp/transactions" : "/study/aes"));
const todayMorePath = computed(() => "/subjects");
const overdueMorePath = computed(() => "/subjects");
const actionMorePath = computed(() => {
if (role.value === "IMP") return "/study/imp/transactions";
if (role.value === "PM" || role.value === "ADMIN") return "/study/finance";
return "/study/aes";
if (role.value === "PM" || role.value === "ADMIN") return "/finance/contracts";
return "/subjects";
});
const isOverdue = (d?: string | null) => {
@@ -164,23 +161,15 @@ const toAeItem = (ae: any): WorkItem => ({
subtitle: study.currentStudy?.name || "",
status: ae.status,
overdue: isOverdue(ae.expected_resolution_date || ae.updated_at),
path: `/study/aes/${ae.id}`,
path: ae.subject_id ? `/subjects/${ae.subject_id}` : "/subjects",
});
const toFinanceItem = (fi: any): WorkItem => ({
title: fi.title || "费用",
const toContractItem = (fi: any): WorkItem => ({
title: fi.contract_no || "合同费用",
subtitle: study.currentStudy?.name || "",
status: fi.status,
status: fi.site_name,
overdue: false,
path: `/study/finance/${fi.id}`,
});
const toImpItem = (tx: any): WorkItem => ({
title: tx.type || "药品记录",
subtitle: study.currentStudy?.name || "",
status: tx.status || "",
overdue: false,
path: "/study/imp/transactions",
path: `/finance/contracts/${fi.id}`,
});
const loadData = async () => {
@@ -189,17 +178,13 @@ const loadData = async () => {
try {
const studyId = currentStudyId.value;
const aesReq = fetchAes(studyId, { status: "NEW", limit: 50 });
const financeReq = fetchFinanceItems(studyId, { status: "SUBMITTED", limit: 50 });
const impReq = fetchImpTransactions ? fetchImpTransactions(studyId, { limit: 50 }) : Promise.resolve({ data: [] });
const [aesRes, financeRes, impRes] = await Promise.allSettled([aesReq, financeReq, impReq]);
const [aesRes, financeRes] = await Promise.allSettled([aesReq, listFinanceContracts(studyId, { limit: 50 })]);
const aes = aesRes.status === "fulfilled" ? (aesRes.value.data.items || aesRes.value.data || []) : [];
const finances =
financeRes.status === "fulfilled" ? (financeRes.value.data.items || financeRes.value.data || []) : [];
const impData = impRes.status === "fulfilled" ? (impRes.value as any).data : [];
const impTx = Array.isArray(impData) ? impData : (impData?.items || []);
buildSections(aes, finances, impTx);
buildSections(aes, finances);
} catch (e: any) {
ElMessage.error(e?.response?.data?.message || "工作台数据加载失败");
} finally {
@@ -207,13 +192,13 @@ const loadData = async () => {
}
};
const buildSections = (aes: any[], finances: any[], impTx: any[]) => {
const buildSections = (aes: any[], finances: any[]) => {
todayList.value = [];
overdueList.value = [];
actionList.value = [];
const openAes = aes.filter((a) => a.status !== "CLOSED");
const financePending = finances.filter((f) => f.status !== "PAID");
const financePending = finances;
const overdueAes = openAes.filter((a) => isOverdue(a.expected_resolution_date || a.updated_at));
if (role.value === "CRA") {
@@ -223,17 +208,13 @@ const buildSections = (aes: any[], finances: any[], impTx: any[]) => {
} else if (role.value === "PM" || role.value === "ADMIN") {
todayList.value = openAes.slice(0, 5).map(toAeItem);
overdueList.value = overdueAes.slice(0, 5).map(toAeItem);
const financeTodo = financePending.slice(0, 3).map(toFinanceItem);
const financeTodo = financePending.slice(0, 3).map(toContractItem);
actionList.value = [...financeTodo, ...openAes.slice(0, 2).map(toAeItem)].slice(0, 5);
} else if (role.value === "PV") {
const aeOpen = aes.filter((a) => a.status !== "CLOSED");
todayList.value = aeOpen.slice(0, 5).map(toAeItem);
overdueList.value = aeOpen.filter((a) => isOverdue(a.updated_at)).slice(0, 5).map(toAeItem);
actionList.value = aeOpen.slice(0, 5).map(toAeItem);
} else if (role.value === "IMP") {
actionList.value = impTx.slice(0, 5).map(toImpItem);
todayList.value = [];
overdueList.value = [];
}
};