前端UI界面美化-2
This commit is contained in:
@@ -1,71 +1,90 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<el-card class="mb-12">
|
||||
<div class="header">
|
||||
<div>
|
||||
<h2>我的工作台</h2>
|
||||
<div class="sub">基于当前角色,为你聚合最需要处理的事项</div>
|
||||
</div>
|
||||
<el-tag type="info">{{ roleLabel }}</el-tag>
|
||||
<div class="workbench-container">
|
||||
<!-- 头部欢迎区域 -->
|
||||
<div class="workbench-header">
|
||||
<div class="header-content">
|
||||
<h1 class="welcome-title">我的工作台</h1>
|
||||
<p class="welcome-subtitle">欢迎回来,这是基于您的角色 <strong>{{ roleLabel }}</strong> 聚合的待办事项</p>
|
||||
</div>
|
||||
<div v-if="!currentStudyId" class="empty-tip">
|
||||
请选择一个项目后查看工作台内容
|
||||
<div class="header-actions">
|
||||
<el-tag effect="dark" class="role-badge">{{ roleLabel }}</el-tag>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 提示未选择项目 -->
|
||||
<el-alert
|
||||
v-if="!currentStudyId"
|
||||
title="请先在顶部导航或项目列表中选择一个项目,以开启完整的工作台功能。"
|
||||
type="warning"
|
||||
show-icon
|
||||
:closable="false"
|
||||
class="study-alert"
|
||||
/>
|
||||
|
||||
<template v-if="currentStudyId">
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<!-- 核心看板层 -->
|
||||
<div class="stats-row">
|
||||
<div class="stats-col">
|
||||
<SectionCard
|
||||
title="今日待办"
|
||||
:items="todayList"
|
||||
:loading="loading"
|
||||
empty-text="暂无需要你处理的事项"
|
||||
empty-text="今天暂时没有需要紧急处理的业务事项"
|
||||
@more="goMore(todayMorePath)"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
</div>
|
||||
<div class="stats-col">
|
||||
<SectionCard
|
||||
title="已逾期"
|
||||
title="已逾期事项"
|
||||
:items="overdueList"
|
||||
:loading="loading"
|
||||
empty-text="暂无需要你处理的事项"
|
||||
empty-text="太棒了!目前没有任何逾期待处理的事项"
|
||||
@more="goMore(overdueMorePath)"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-row :gutter="12" class="mt-12">
|
||||
<el-col :span="12">
|
||||
<!-- 次级看板与快捷入口 -->
|
||||
<div class="secondary-row">
|
||||
<div class="action-col">
|
||||
<SectionCard
|
||||
title="需要我处理的事项"
|
||||
:items="actionList"
|
||||
:loading="loading"
|
||||
empty-text="暂无需要你处理的事项"
|
||||
empty-text="暂无需要您处理的事项"
|
||||
@more="goMore(actionMorePath)"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card shadow="never" class="section-card">
|
||||
<div class="section-header">
|
||||
<span>快捷入口</span>
|
||||
<el-button type="primary" link @click="goMore('/study/home')">进入项目</el-button>
|
||||
</div>
|
||||
<div class="quick-list">
|
||||
<el-button v-for="qa in quickActions" :key="qa.path" type="primary" plain size="small" @click="goMore(qa.path)">
|
||||
{{ qa.label }}
|
||||
</div>
|
||||
<div class="quick-col">
|
||||
<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-icon class="el-icon--right"><Right /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-if="!quickActions.length" class="empty">{{ emptyText }}</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="quick-grid">
|
||||
<div
|
||||
v-for="qa in quickActions"
|
||||
:key="qa.path"
|
||||
class="quick-item"
|
||||
@click="goMore(qa.path)"
|
||||
>
|
||||
<div class="quick-icon-box">
|
||||
<el-icon><component :is="qa.icon" /></el-icon>
|
||||
</div>
|
||||
<span class="quick-label">{{ qa.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!quickActions.length" class="empty-quick">{{ emptyText }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-row v-if="showPersonalTodo" class="mt-12">
|
||||
<el-col :span="24">
|
||||
<PersonalTodo :storage-key="todoStorageKey" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 个人备忘层 -->
|
||||
<div v-if="showPersonalTodo" class="todo-row">
|
||||
<PersonalTodo :storage-key="todoStorageKey" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -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 () => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
padding: 16px;
|
||||
}
|
||||
.header {
|
||||
.workbench-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
.sub {
|
||||
color: #666;
|
||||
|
||||
.workbench-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.welcome-title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--ctms-text-main);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.welcome-subtitle {
|
||||
margin: 8px 0 0;
|
||||
font-size: 15px;
|
||||
color: var(--ctms-text-secondary);
|
||||
}
|
||||
|
||||
.role-badge {
|
||||
background-color: var(--ctms-primary);
|
||||
border: none;
|
||||
font-weight: 600;
|
||||
padding: 0 12px;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.stats-row, .secondary-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.todo-row {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.study-alert {
|
||||
border: none;
|
||||
border-left: 4px solid var(--ctms-warning);
|
||||
background-color: #fffbe6;
|
||||
}
|
||||
|
||||
/* 快捷入口卡片样式 */
|
||||
.quick-entry-card {
|
||||
background-color: #fff;
|
||||
border: 1px solid var(--ctms-border-color);
|
||||
border-radius: 8px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.entry-header {
|
||||
padding: 16px 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--ctms-border-color);
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.entry-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--ctms-text-main);
|
||||
}
|
||||
|
||||
.entry-more {
|
||||
font-size: 13px;
|
||||
}
|
||||
.empty-tip {
|
||||
padding: 12px 0;
|
||||
color: #888;
|
||||
|
||||
.quick-grid {
|
||||
padding: 20px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
.section-card {
|
||||
min-height: 220px;
|
||||
}
|
||||
.section-header {
|
||||
|
||||
.quick-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.quick-list {
|
||||
|
||||
.quick-item:hover {
|
||||
background-color: var(--ctms-primary-light);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.quick-icon-box {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: #fff;
|
||||
border: 1px solid var(--ctms-border-color);
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
color: var(--ctms-primary);
|
||||
box-shadow: var(--ctms-shadow-sm);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.empty {
|
||||
color: #888;
|
||||
|
||||
.quick-item:hover .quick-icon-box {
|
||||
border-color: var(--ctms-primary);
|
||||
background-color: var(--ctms-primary);
|
||||
color: #fff;
|
||||
}
|
||||
.mt-12 {
|
||||
margin-top: 12px;
|
||||
|
||||
.quick-label {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--ctms-text-regular);
|
||||
}
|
||||
.mb-12 {
|
||||
margin-bottom: 12px;
|
||||
|
||||
.empty-quick {
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
color: var(--ctms-text-disabled);
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.stats-row, .secondary-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user