前端UI界面美化-2

This commit is contained in:
Cheng Zhou
2025-12-22 08:54:34 +08:00
parent 4f510871bf
commit 03fddba406
8 changed files with 873 additions and 257 deletions
+84 -19
View File
@@ -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>
+13 -4
View File
@@ -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 {
+108 -15
View File
@@ -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>
+56 -8
View File
@@ -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>