前端UI界面美化-2
This commit is contained in:
@@ -1,9 +1,23 @@
|
||||
<template>
|
||||
<el-card class="kpi-card" shadow="hover">
|
||||
<div class="title">{{ title }}</div>
|
||||
<div class="value" v-if="!loading">{{ value }}</div>
|
||||
<div class="value loading" v-else>加载中...</div>
|
||||
<div class="subtext" v-if="subtext">{{ subtext }}</div>
|
||||
<el-card class="kpi-card" shadow="never">
|
||||
<div class="kpi-header">
|
||||
<span class="kpi-title">{{ title }}</span>
|
||||
<el-icon v-if="icon" class="kpi-icon"><component :is="icon" /></el-icon>
|
||||
</div>
|
||||
<div class="kpi-content">
|
||||
<div v-if="loading" class="kpi-loading">
|
||||
<el-skeleton animated>
|
||||
<template #template>
|
||||
<el-skeleton-item variant="h1" style="width: 60%" />
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</div>
|
||||
<div v-else class="kpi-value-container">
|
||||
<span class="kpi-value">{{ value }}</span>
|
||||
<span v-if="unit" class="kpi-unit">{{ unit }}</span>
|
||||
</div>
|
||||
<div v-if="subtext" class="kpi-subtext">{{ subtext }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
@@ -13,6 +27,8 @@ interface Props {
|
||||
value: string | number;
|
||||
subtext?: string;
|
||||
loading?: boolean;
|
||||
icon?: any;
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
@@ -20,22 +36,71 @@ defineProps<Props>();
|
||||
|
||||
<style scoped>
|
||||
.kpi-card {
|
||||
min-height: 120px;
|
||||
border: 1px solid var(--ctms-border-color);
|
||||
transition: all 0.3s ease;
|
||||
height: 100%;
|
||||
}
|
||||
.title {
|
||||
color: #909399;
|
||||
|
||||
.kpi-card:hover {
|
||||
border-color: var(--ctms-primary-hover);
|
||||
box-shadow: var(--ctms-shadow-md) !important;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.kpi-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.kpi-title {
|
||||
color: var(--ctms-text-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.kpi-icon {
|
||||
font-size: 18px;
|
||||
color: var(--ctms-primary);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.kpi-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.kpi-value-container {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.kpi-value {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: var(--ctms-text-main);
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.kpi-unit {
|
||||
font-size: 14px;
|
||||
color: var(--ctms-text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.kpi-subtext {
|
||||
color: var(--ctms-text-secondary);
|
||||
font-size: 13px;
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px dashed var(--ctms-border-color);
|
||||
}
|
||||
.value {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
margin: 8px 0;
|
||||
}
|
||||
.subtext {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
.loading {
|
||||
color: #c0c4cc;
|
||||
|
||||
.kpi-loading {
|
||||
height: 38px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -90,9 +90,7 @@
|
||||
<el-container class="main-container">
|
||||
<el-header class="layout-header">
|
||||
<div class="header-left">
|
||||
<div class="breadcrumb-study" v-if="study.currentStudy">
|
||||
<el-tag effect="plain" class="study-tag">{{ study.currentStudy.name }}</el-tag>
|
||||
</div>
|
||||
<!-- 移除冗余的 study-tag,由 StudySelector 统一展示交互 -->
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<StudySelector />
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,35 +1,128 @@
|
||||
<template>
|
||||
<el-card>
|
||||
<div class="actions">
|
||||
<el-button v-for="item in actions" :key="item.path" type="primary" plain @click="go(item.path)">
|
||||
{{ item.label }}
|
||||
</el-button>
|
||||
<div class="quick-actions-section">
|
||||
<div class="section-header">
|
||||
<el-icon class="header-icon"><Pointer /></el-icon>
|
||||
<span class="header-title">通往业务模块的快捷入口</span>
|
||||
</div>
|
||||
</el-card>
|
||||
<div class="actions-grid">
|
||||
<div
|
||||
v-for="item in actions"
|
||||
:key="item.path"
|
||||
class="action-item"
|
||||
@click="go(item.path)"
|
||||
>
|
||||
<div class="action-icon-wrapper" :style="{ backgroundColor: item.color + '20', color: item.color }">
|
||||
<el-icon><component :is="item.icon" /></el-icon>
|
||||
</div>
|
||||
<span class="action-label">{{ item.label }}</span>
|
||||
<el-icon class="action-arrow"><ArrowRight /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from "vue-router";
|
||||
import {
|
||||
Pointer, ArrowRight, Timer, UserFilled, Warning,
|
||||
QuestionFilled, Management, Money, Collection
|
||||
} from "@element-plus/icons-vue";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const actions = [
|
||||
{ label: "伦理与启动", path: "/study/milestones" },
|
||||
{ label: "受试者", path: "/study/subjects" },
|
||||
{ label: "AE", path: "/study/aes" },
|
||||
{ label: "数据问题", path: "/study/data-queries" },
|
||||
{ label: "药品", path: "/study/imp/inventory" },
|
||||
{ label: "费用", path: "/study/finance" },
|
||||
{ label: "FAQ", path: "/study/faq" },
|
||||
{ label: "里程碑管理", path: "/study/milestones", icon: Timer, color: "#2f54eb" },
|
||||
{ label: "受试者", path: "/study/subjects", icon: UserFilled, color: "#722ed1" },
|
||||
{ label: "不良事件", path: "/study/aes", icon: Warning, color: "#faad14" },
|
||||
{ label: "数据问题", path: "/study/data-queries", icon: QuestionFilled, color: "#1890ff" },
|
||||
{ label: "药品库存", path: "/study/imp/inventory", icon: Management, color: "#52c41a" },
|
||||
{ label: "费用治理", path: "/study/finance", icon: Money, color: "#eb2f96" },
|
||||
{ label: "项目知识库", path: "/study/faq", icon: Collection, color: "#fa8c16" },
|
||||
];
|
||||
|
||||
const go = (path: string) => router.push(path);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.actions {
|
||||
.quick-actions-section {
|
||||
background-color: #fff;
|
||||
border-radius: var(--ctms-radius);
|
||||
padding: 24px;
|
||||
box-shadow: var(--ctms-shadow-sm);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
font-size: 18px;
|
||||
color: var(--ctms-primary);
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--ctms-text-main);
|
||||
}
|
||||
|
||||
.actions-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.action-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
background-color: #fafafa;
|
||||
border: 1px solid var(--ctms-border-color);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.action-item:hover {
|
||||
background-color: #fff;
|
||||
border-color: var(--ctms-primary-hover);
|
||||
box-shadow: var(--ctms-shadow-sm);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.action-icon-wrapper {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.action-label {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--ctms-text-main);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.action-arrow {
|
||||
font-size: 14px;
|
||||
color: var(--ctms-text-disabled);
|
||||
opacity: 0;
|
||||
transition: all 0.3s ease;
|
||||
transform: translateX(-10px);
|
||||
}
|
||||
|
||||
.action-item:hover .action-arrow {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
<template>
|
||||
<el-dropdown>
|
||||
<span class="el-dropdown-link">
|
||||
<el-tag size="small" type="info">
|
||||
{{ study.currentStudy?.name || "未选择项目" }}
|
||||
</el-tag>
|
||||
</span>
|
||||
<el-dropdown trigger="click" class="study-selector-dropdown">
|
||||
<div class="selector-trigger">
|
||||
<el-icon class="trigger-icon"><OfficeBuilding /></el-icon>
|
||||
<span class="current-study-name">{{ study.currentStudy?.name || "选择项目" }}</span>
|
||||
<el-icon class="arrow-icon"><ArrowDown /></el-icon>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="goSelect">切换项目</el-dropdown-item>
|
||||
<el-dropdown-item @click="clearStudy">清除当前项目</el-dropdown-item>
|
||||
<el-dropdown-item @click="goSelect">
|
||||
<el-icon><Switch /></el-icon>切换项目
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item divided @click="clearStudy" class="danger-item">
|
||||
<el-icon><Close /></el-icon>退出当前项目
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
@@ -17,6 +21,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from "vue-router";
|
||||
import { useStudyStore } from "../store/study";
|
||||
import { OfficeBuilding, ArrowDown, Switch, Close } from "@element-plus/icons-vue";
|
||||
|
||||
const router = useRouter();
|
||||
const study = useStudyStore();
|
||||
@@ -30,3 +35,46 @@ const clearStudy = () => {
|
||||
router.push("/studies");
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.selector-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 12px;
|
||||
background-color: #fafafa;
|
||||
border: 1px solid var(--ctms-border-color);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.selector-trigger:hover {
|
||||
background-color: var(--ctms-primary-light);
|
||||
border-color: var(--ctms-primary-hover);
|
||||
}
|
||||
|
||||
.trigger-icon {
|
||||
color: var(--ctms-primary);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.current-study-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--ctms-text-main);
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.arrow-icon {
|
||||
font-size: 12px;
|
||||
color: var(--ctms-text-secondary);
|
||||
}
|
||||
|
||||
.danger-item {
|
||||
color: var(--ctms-danger);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user