diff --git a/frontend/src/components/KpiCard.vue b/frontend/src/components/KpiCard.vue index bba3e60f..a4ee7b36 100644 --- a/frontend/src/components/KpiCard.vue +++ b/frontend/src/components/KpiCard.vue @@ -1,9 +1,23 @@ - - {{ title }} - {{ value }} - 加载中... - {{ subtext }} + + + {{ title }} + + + + + + + + + + + + {{ value }} + {{ unit }} + + {{ subtext }} + @@ -13,6 +27,8 @@ interface Props { value: string | number; subtext?: string; loading?: boolean; + icon?: any; + unit?: string; } defineProps(); @@ -20,22 +36,71 @@ defineProps(); diff --git a/frontend/src/components/Layout.vue b/frontend/src/components/Layout.vue index 0508068e..4312707c 100644 --- a/frontend/src/components/Layout.vue +++ b/frontend/src/components/Layout.vue @@ -90,9 +90,7 @@ - - {{ study.currentStudy.name }} - + @@ -215,11 +213,22 @@ const onLogout = () => { :deep(.el-sub-menu__title) { color: rgba(255, 255, 255, 0.65) !important; margin: 4px 12px; + border-radius: var(--ctms-radius); } :deep(.el-sub-menu__title:hover) { color: #fff !important; - background-color: transparent !important; + background-color: rgba(255, 255, 255, 0.1) !important; +} + +:deep(.el-menu--inline) { + background-color: rgba(0, 0, 0, 0.2) !important; + margin: 0 !important; +} + +:deep(.el-menu--inline .el-menu-item) { + padding-left: 48px !important; + margin: 2px 12px; } .layout-header { diff --git a/frontend/src/components/QuickActions.vue b/frontend/src/components/QuickActions.vue index 3d786196..a324c3c0 100644 --- a/frontend/src/components/QuickActions.vue +++ b/frontend/src/components/QuickActions.vue @@ -1,35 +1,128 @@ - - - - {{ item.label }} - + + + + 通往业务模块的快捷入口 - + + + + + + {{ item.label }} + + + + diff --git a/frontend/src/components/StudySelector.vue b/frontend/src/components/StudySelector.vue index d73af61b..c5d68a79 100644 --- a/frontend/src/components/StudySelector.vue +++ b/frontend/src/components/StudySelector.vue @@ -1,14 +1,18 @@ - - - - {{ study.currentStudy?.name || "未选择项目" }} - - + + + + {{ study.currentStudy?.name || "选择项目" }} + + - 切换项目 - 清除当前项目 + + 切换项目 + + + 退出当前项目 + @@ -17,6 +21,7 @@ + + diff --git a/frontend/src/views/StudyHome.vue b/frontend/src/views/StudyHome.vue index 9f6c6bcd..789d8d75 100644 --- a/frontend/src/views/StudyHome.vue +++ b/frontend/src/views/StudyHome.vue @@ -1,50 +1,89 @@ - - - - 项目概览 - - {{ study.currentStudy.name }}({{ study.currentStudy.code }}) + + + + + 项目概览 + + {{ study.currentStudy.name }} + / + {{ study.currentStudy.code }} + + 我的角色: {{ auth.user?.role }} + - - + + + - - - - - - - - + + + + + + + + - - + + + + - + + + + - - + + + @@ -55,6 +94,7 @@ import { useStudyStore } from "../store/study"; import { fetchFinanceSummary, fetchOverdueAesCount, fetchOverdueQueriesCount, fetchProgress } from "../api/dashboard"; import KpiCard from "../components/KpiCard.vue"; import QuickActions from "../components/QuickActions.vue"; +import { List, Warning, QuestionFilled, Money } from "@element-plus/icons-vue"; const auth = useAuthStore(); const study = useStudyStore(); @@ -73,19 +113,13 @@ const loading = ref({ const milestoneCompletionRate = computed(() => { const total = progress.value?.milestones_total ?? 0; - if (!total) return "-"; - return formatPercent((progress.value?.milestones_done ?? 0) / total); + if (!total) return "暂无节点"; + const rate = (progress.value?.milestones_done ?? 0) / total; + return `总体进度: ${(rate * 100).toFixed(0)}%`; }); -const milestoneCompletionText = computed(() => { - const total = progress.value?.milestones_total ?? 0; - const done = progress.value?.milestones_done ?? 0; - return total ? `已完成 ${done}/${total}` : "暂无节点数据"; -}); - -const formatPercent = (v?: number | null) => { - if (v === undefined || v === null) return "-"; - return `${(v * 100).toFixed(0)}%`; +const formatAmount = (val: number) => { + return new Intl.NumberFormat('zh-CN', { minimumFractionDigits: 2 }).format(val); }; const loadData = async () => { @@ -105,18 +139,10 @@ const loadData = async () => { fetchOverdueQueriesCount(studyId), ]); - if (pRes.status === "fulfilled") { - progress.value = pRes.value.data; - } - if (fRes.status === "fulfilled") { - financeSummary.value = fRes.value.data; - } - if (aesRes.status === "fulfilled") { - overdueAes.value = aesRes.value.data?.total ?? 0; - } - if (dqRes.status === "fulfilled") { - overdueQueries.value = dqRes.value.data?.total ?? 0; - } + if (pRes.status === "fulfilled") progress.value = pRes.value.data; + if (fRes.status === "fulfilled") financeSummary.value = fRes.value.data; + if (aesRes.status === "fulfilled") overdueAes.value = aesRes.value.data?.total ?? 0; + if (dqRes.status === "fulfilled") overdueQueries.value = dqRes.value.data?.total ?? 0; } finally { loading.value.progress = false; loading.value.finance = false; @@ -131,29 +157,69 @@ onMounted(() => { diff --git a/frontend/src/views/workbench/MyWorkbench.vue b/frontend/src/views/workbench/MyWorkbench.vue index e0ca87ca..76f16691 100644 --- a/frontend/src/views/workbench/MyWorkbench.vue +++ b/frontend/src/views/workbench/MyWorkbench.vue @@ -1,71 +1,90 @@ - - - - - 我的工作台 - 基于当前角色,为你聚合最需要处理的事项 - - {{ roleLabel }} + + + + + 我的工作台 + 欢迎回来,这是基于您的角色 {{ roleLabel }} 聚合的待办事项 - - 请选择一个项目后查看工作台内容 + + {{ roleLabel }} - + + + + - - + + + - - + + - - + + - - + + + - - - - - 快捷入口 - 进入项目 - - - - {{ qa.label }} + + + + + 快捷入口 + + 进入项目 - {{ emptyText }} - - - + + + + + + {{ qa.label }} + + + {{ emptyText }} + + + - - - - - + + + + @@ -79,6 +98,7 @@ 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"; interface WorkItem { title: string; @@ -112,18 +132,18 @@ const quickActions = computed(() => { if (!currentStudyId.value) return []; if (role.value === "CRA") return [ - { label: "伦理与启动", path: "/study/milestones" }, - { label: "AE 列表", path: "/study/aes" }, + { label: "伦理启动", path: "/study/milestones", icon: Timer }, + { label: "AE 列表", path: "/study/aes", icon: Warning }, ]; if (role.value === "PM") return [ - { label: "伦理与启动", path: "/study/milestones" }, - { label: "费用审核", path: "/study/finance" }, - { label: "公告维护", path: "/study/faq" }, + { label: "伦理启动", path: "/study/milestones", icon: Timer }, + { label: "费用审核", path: "/study/finance", icon: Money }, + { label: "公告管理", path: "/study/faq", icon: Collection }, ]; - if (role.value === "PV") return [{ label: "AE 列表", path: "/study/aes" }]; - if (role.value === "IMP") return [{ label: "药品台账", path: "/study/imp/transactions" }]; - return [{ label: "伦理与启动", path: "/study/milestones" }]; + 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 }]; }); const todayMorePath = computed(() => (role.value === "IMP" ? "/study/imp/transactions" : "/study/aes")); @@ -176,7 +196,8 @@ const loadData = async () => { 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 impTx = impRes.status === "fulfilled" ? (impRes.value.data.items || impRes.value.data || []) : []; + const impData = impRes.status === "fulfilled" ? (impRes.value as any).data : []; + const impTx = Array.isArray(impData) ? impData : (impData?.items || []); buildSections(aes, finances, impTx); } catch (e: any) { @@ -193,7 +214,6 @@ const buildSections = (aes: any[], finances: any[], impTx: any[]) => { const openAes = aes.filter((a) => a.status !== "CLOSED"); const financePending = finances.filter((f) => f.status !== "PAID"); - const overdueAes = openAes.filter((a) => isOverdue(a.expected_resolution_date || a.updated_at)); if (role.value === "CRA") { @@ -231,43 +251,144 @@ onMounted(async () => { diff --git a/frontend/src/views/workbench/components/PersonalTodo.vue b/frontend/src/views/workbench/components/PersonalTodo.vue index 8ae9423f..e2c18d00 100644 --- a/frontend/src/views/workbench/components/PersonalTodo.vue +++ b/frontend/src/views/workbench/components/PersonalTodo.vue @@ -1,24 +1,65 @@ - - 个人待办(本地) - - - - 添加 - - - - {{ item.text }} - 删除 + + + + + 个人备忘 (本地存储) + + + {{ todos.length }} 条记录 + + + + + + + + + + + + + 添加 + + + + + + + + {{ item.text }} + + + + + + + - 暂无待办,可添加你的个人备忘
- {{ study.currentStudy.name }}({{ study.currentStudy.code }}) +
+ {{ study.currentStudy.name }} + / + {{ study.currentStudy.code }}
欢迎回来,这是基于您的角色 {{ roleLabel }} 聚合的待办事项