未知(继上次中断)
This commit is contained in:
@@ -1,29 +1,36 @@
|
||||
<template>
|
||||
<el-card class="kpi-card" :class="[`type-${type}`]" shadow="hover">
|
||||
<div class="kpi-header">
|
||||
<div class="kpi-title-wrapper">
|
||||
<div class="kpi-icon-wrapper" v-if="icon">
|
||||
<el-icon class="kpi-icon"><component :is="icon" /></el-icon>
|
||||
<div class="kpi-card" :class="[`type-${type}`]">
|
||||
<!-- 顶部标签区域 -->
|
||||
<div class="kpi-badge">
|
||||
<div class="kpi-badge-icon" v-if="icon">
|
||||
<el-icon><component :is="icon" /></el-icon>
|
||||
</div>
|
||||
<span class="kpi-badge-text">{{ badgeText }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 标题区域 -->
|
||||
<div class="kpi-main">
|
||||
<h3 class="kpi-title">{{ title }}</h3>
|
||||
<p class="kpi-subtitle" v-if="subtext">{{ subtext }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 底部数值区域 -->
|
||||
<div class="kpi-footer">
|
||||
<div class="kpi-value-wrapper">
|
||||
<div v-if="loading" class="kpi-loading">
|
||||
<el-skeleton animated :style="{ '--el-skeleton-color': 'rgba(255,255,255,0.2)', '--el-skeleton-to-color': 'rgba(255,255,255,0.4)' }">
|
||||
<template #template>
|
||||
<el-skeleton-item variant="h1" style="width: 80px; height: 32px" />
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</div>
|
||||
<span class="kpi-title">{{ title }}</span>
|
||||
<template v-else>
|
||||
<span class="kpi-value">{{ value }}</span>
|
||||
<span v-if="unit" class="kpi-unit">{{ unit }}</span>
|
||||
</template>
|
||||
</div>
|
||||
<slot name="header-suffix"></slot>
|
||||
</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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -37,123 +44,186 @@ interface Props {
|
||||
icon?: any;
|
||||
unit?: string;
|
||||
type?: 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info';
|
||||
badge?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
type: 'default',
|
||||
});
|
||||
|
||||
// 根据类型生成默认徽章文字
|
||||
const badgeText = computed(() => {
|
||||
if (props.badge) return props.badge;
|
||||
const badgeMap: Record<string, string> = {
|
||||
primary: '统计',
|
||||
success: '支出',
|
||||
warning: '待办',
|
||||
danger: '警告',
|
||||
info: '信息',
|
||||
default: '概览',
|
||||
};
|
||||
return badgeMap[props.type] || '概览';
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.kpi-card {
|
||||
border: 1px solid var(--ctms-border-color);
|
||||
transition: all 0.3s ease;
|
||||
height: 100%;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
--el-card-padding: 12px 16px;
|
||||
}
|
||||
|
||||
.kpi-card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 4px;
|
||||
padding: 20px;
|
||||
border-radius: 16px;
|
||||
height: 100%;
|
||||
background-color: transparent;
|
||||
transition: background-color 0.3s ease;
|
||||
min-height: 140px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.kpi-card.type-primary::before { background-color: var(--el-color-primary); }
|
||||
.kpi-card.type-success::before { background-color: var(--el-color-success); }
|
||||
.kpi-card.type-warning::before { background-color: var(--el-color-warning); }
|
||||
.kpi-card.type-danger::before { background-color: var(--el-color-danger); }
|
||||
.kpi-card.type-info::before { background-color: var(--el-color-info); }
|
||||
/* 默认样式 - 深灰色 */
|
||||
.kpi-card.type-default {
|
||||
background: linear-gradient(135deg, #2d2d2d 0%, #1a1a1a 100%);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 主要样式 - 蓝色渐变 */
|
||||
.kpi-card.type-primary {
|
||||
background: linear-gradient(135deg, #4ECDC4 0%, #44A8B3 50%, #2C8C99 100%);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 成功样式 - 黄/橙色渐变 */
|
||||
.kpi-card.type-success {
|
||||
background: linear-gradient(135deg, #F7DC6F 0%, #F4C430 50%, #E6B800 100%);
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
/* 警告样式 - 红/珊瑚色渐变 */
|
||||
.kpi-card.type-warning {
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #EE5A52 50%, #DC4040 100%);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 危险样式 - 深红色渐变 */
|
||||
.kpi-card.type-danger {
|
||||
background: linear-gradient(135deg, #C0392B 0%, #96281B 100%);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 信息样式 - 深灰色 */
|
||||
.kpi-card.type-info {
|
||||
background: linear-gradient(135deg, #3d3d3d 0%, #2a2a2a 100%);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 悬停效果 */
|
||||
.kpi-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.kpi-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
/* 顶部徽章 */
|
||||
.kpi-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
gap: 6px;
|
||||
padding: 4px 10px;
|
||||
border-radius: 6px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
backdrop-filter: blur(4px);
|
||||
width: fit-content;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.kpi-title-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
.type-success .kpi-badge {
|
||||
background: rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.kpi-icon-wrapper {
|
||||
.kpi-badge-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 8px;
|
||||
background-color: var(--ctms-bg-color-page);
|
||||
color: var(--ctms-text-secondary);
|
||||
font-size: 12px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.type-primary .kpi-icon-wrapper { color: var(--el-color-primary); background-color: var(--el-color-primary-light-9); }
|
||||
.type-success .kpi-icon-wrapper { color: var(--el-color-success); background-color: var(--el-color-success-light-9); }
|
||||
.type-warning .kpi-icon-wrapper { color: var(--el-color-warning); background-color: var(--el-color-warning-light-9); }
|
||||
.type-danger .kpi-icon-wrapper { color: var(--el-color-danger); background-color: var(--el-color-danger-light-9); }
|
||||
.type-info .kpi-icon-wrapper { color: var(--el-color-info); background-color: var(--el-color-info-light-9); }
|
||||
|
||||
.kpi-title {
|
||||
color: var(--ctms-text-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
.kpi-badge-text {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.kpi-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.kpi-content {
|
||||
/* 主内容区域 */
|
||||
.kpi-main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.kpi-value-container {
|
||||
.kpi-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
line-height: 1.3;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.kpi-subtitle {
|
||||
font-size: 13px;
|
||||
margin: 0;
|
||||
opacity: 0.75;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* 底部数值区域 */
|
||||
.kpi-footer {
|
||||
margin-top: auto;
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.kpi-value-wrapper {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.kpi-value {
|
||||
font-size: 32px;
|
||||
font-size: 36px;
|
||||
font-weight: 700;
|
||||
color: var(--ctms-text-main);
|
||||
line-height: 1.2;
|
||||
font-family: var(--el-font-family);
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
|
||||
.kpi-unit {
|
||||
font-size: 14px;
|
||||
color: var(--ctms-text-secondary);
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.kpi-subtext {
|
||||
color: var(--ctms-text-secondary);
|
||||
font-size: 13px;
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid var(--ctms-border-color-lighter);
|
||||
opacity: 0.8;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.kpi-loading {
|
||||
height: 38px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.kpi-card {
|
||||
min-height: 120px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.kpi-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.kpi-value {
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<el-container class="layout-container">
|
||||
<el-aside :width="isCollapsed ? '72px' : '240px'" class="layout-aside" :class="{ collapsed: isCollapsed }" v-if="isAdmin || study.currentStudy">
|
||||
<el-aside :width="isCollapsed ? '68px' : '200px'" class="layout-aside" :class="{ collapsed: isCollapsed }" v-if="isAdmin || study.currentStudy">
|
||||
<div class="aside-logo">
|
||||
<div class="logo-icon"></div>
|
||||
<span class="logo-text">{{ TEXT.common.appName }}</span>
|
||||
@@ -100,9 +100,50 @@
|
||||
</svg>
|
||||
</el-icon>
|
||||
</el-button>
|
||||
|
||||
<!-- 移入 Header 的面包屑 -->
|
||||
<div v-if="breadcrumbs.length" class="header-breadcrumb">
|
||||
<template v-for="(item, index) in breadcrumbs" :key="index">
|
||||
<!-- 带下拉的选择器类型 -->
|
||||
<el-dropdown v-if="item.hasDropdown" trigger="click" @command="handleBreadcrumbCommand">
|
||||
<div class="breadcrumb-item-wrapper is-link">
|
||||
<span class="breadcrumb-item-text is-link-text">{{ item.label }}</span>
|
||||
<el-icon class="breadcrumb-dropdown-icon"><ArrowDown /></el-icon>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu class="breadcrumb-dropdown-menu">
|
||||
<template v-if="item.type === 'study'">
|
||||
<el-dropdown-item v-for="s in studies" :key="s.id" :command="{ type: 'study', value: s }" :class="{ active: study.currentStudy?.id === s.id }">
|
||||
{{ s.name }}
|
||||
</el-dropdown-item>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'site'">
|
||||
<el-dropdown-item :command="{ type: 'site', value: null }" :class="{ active: !study.currentSite }">
|
||||
{{ TEXT.common.labels.allSites || '所有中心' }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-for="s in sites" :key="s.id" :command="{ type: 'site', value: s }" :class="{ active: study.currentSite?.id === s.id }">
|
||||
{{ s.name }}
|
||||
</el-dropdown-item>
|
||||
</template>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
|
||||
<!-- 普通链接或静态文本 -->
|
||||
<div
|
||||
v-else
|
||||
class="breadcrumb-item-wrapper"
|
||||
:class="{ 'is-link': item.path && index < breadcrumbs.length - 1 }"
|
||||
@click="item.path && index < breadcrumbs.length - 1 && router.push(item.path)"
|
||||
>
|
||||
<span class="breadcrumb-item-text" :class="{ 'is-link-text': index > 0 && index < breadcrumbs.length - 1 }">{{ item.label }}</span>
|
||||
</div>
|
||||
|
||||
<span v-if="index < breadcrumbs.length - 1" class="breadcrumb-separator">/</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<StudySelector />
|
||||
<el-dropdown trigger="click" @command="onCommand" class="user-profile">
|
||||
<span class="dropdown-trigger compact">
|
||||
<el-avatar
|
||||
@@ -131,6 +172,7 @@
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</el-header>
|
||||
|
||||
|
||||
<el-main class="layout-main">
|
||||
<div class="content-wrapper">
|
||||
@@ -146,16 +188,18 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue";
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useAuthStore } from "../store/auth";
|
||||
import { useStudyStore } from "../store/study";
|
||||
import StudySelector from "./StudySelector.vue";
|
||||
import { fetchStudies } from "../api/studies";
|
||||
import { fetchSites } from "../api/sites";
|
||||
import { TEXT } from "../locales";
|
||||
import {
|
||||
Monitor, User, Suitcase, House, Calendar, Flag, ChatDotRound,
|
||||
CircleCheck, Box, Coin, Notebook, Document, ArrowDown, SwitchButton, Files
|
||||
} from "@element-plus/icons-vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
const auth = useAuthStore();
|
||||
const study = useStudyStore();
|
||||
@@ -189,6 +233,121 @@ const activeMenu = computed(() => {
|
||||
return path;
|
||||
});
|
||||
|
||||
const studies = ref<any[]>([]);
|
||||
const sites = ref<any[]>([]);
|
||||
|
||||
const loadStudies = async () => {
|
||||
try {
|
||||
const { data } = await fetchStudies();
|
||||
studies.value = Array.isArray(data) ? data : (data as any).items || [];
|
||||
} catch {}
|
||||
};
|
||||
|
||||
const loadSites = async () => {
|
||||
if (!study.currentStudy) {
|
||||
sites.value = [];
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const { data } = await fetchSites(study.currentStudy.id, { limit: 500 });
|
||||
sites.value = Array.isArray(data) ? data : (data as any).items || [];
|
||||
} catch {
|
||||
sites.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
const breadcrumbs = computed(() => {
|
||||
const items: any[] = [];
|
||||
|
||||
if (route.path.startsWith("/admin")) {
|
||||
items.push({
|
||||
label: TEXT.menu.admin,
|
||||
path: "/admin/users"
|
||||
});
|
||||
} else if (study.currentStudy) {
|
||||
// 1. 项目名
|
||||
items.push({
|
||||
label: (study.currentStudy.name || "").replace(/^示例项目[::]/, ''),
|
||||
path: "/project/overview",
|
||||
hasDropdown: true,
|
||||
type: 'study'
|
||||
});
|
||||
|
||||
// 2. 中心名 (逻辑增强:详情页优先显示具体中心,列表页显示筛选中心)
|
||||
items.push({
|
||||
label: study.viewContext?.siteName || study.currentSite?.name || "所有中心",
|
||||
hasDropdown: true,
|
||||
type: 'site'
|
||||
});
|
||||
|
||||
// 3. 模块名 (逻辑增强:当在详情页时显示所属模块)
|
||||
const moduleMap: Record<string, { label: string, path: string }> = {
|
||||
"/fees/contracts": { label: TEXT.menu.feeContracts, path: "/fees/contracts" },
|
||||
"/fees/special": { label: TEXT.menu.feeSpecials, path: "/fees/special" },
|
||||
"/drug/shipments": { label: TEXT.menu.drugShipments, path: "/drug/shipments" },
|
||||
"/subjects": { label: TEXT.menu.subjects, path: "/subjects" },
|
||||
"/startup/feasibility": { label: TEXT.menu.startupFeasibilityEthics, path: "/startup/feasibility-ethics" },
|
||||
"/startup/ethics": { label: TEXT.menu.startupFeasibilityEthics, path: "/startup/feasibility-ethics" },
|
||||
"/startup/kickoff": { label: TEXT.menu.startupMeetingAuth, path: "/startup/meeting-auth" },
|
||||
"/startup/training": { label: TEXT.menu.startupMeetingAuth, path: "/startup/meeting-auth" },
|
||||
"/knowledge/medical-consult": { label: TEXT.menu.knowledgeMedicalConsult, path: "/knowledge/medical-consult" },
|
||||
"/knowledge/notes": { label: TEXT.menu.knowledgeNotes, path: "/knowledge/notes" },
|
||||
};
|
||||
|
||||
let matchedModule = null;
|
||||
for (const prefix in moduleMap) {
|
||||
if (route.path.startsWith(prefix)) {
|
||||
matchedModule = moduleMap[prefix];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedModule) {
|
||||
// 如果当前路径不是模块主路径,说明在子页面/详情页,需要把模块名展示出来
|
||||
if (route.path !== matchedModule.path) {
|
||||
items.push({
|
||||
label: matchedModule.label,
|
||||
path: matchedModule.path
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 当前页面标题
|
||||
const title = route.meta?.title as string | undefined;
|
||||
if (title && title !== study.currentStudy?.name && title !== TEXT.menu.projectOverview && title !== (study.viewContext?.siteName || study.currentSite?.name)) {
|
||||
// 检查 title 是否已经作为模块名添加过了
|
||||
const isModuleTitle = items.some(item => item.label === title);
|
||||
if (!isModuleTitle) {
|
||||
items.push({
|
||||
label: title,
|
||||
path: route.path
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
});
|
||||
|
||||
const handleBreadcrumbCommand = (cmd: any) => {
|
||||
if (cmd.type === 'study') {
|
||||
study.setCurrentStudy(cmd.value);
|
||||
router.push("/project/overview");
|
||||
} else if (cmd.type === 'site') {
|
||||
study.setCurrentSite(cmd.value);
|
||||
// 刷新当前页面或根据业务逻辑处理
|
||||
ElMessage.success(`已切换至: ${cmd.value?.name || '所有中心'}`);
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => study.currentStudy, () => {
|
||||
loadSites();
|
||||
}, { immediate: true });
|
||||
|
||||
watch(() => route.path, () => {
|
||||
study.setViewContext(null);
|
||||
});
|
||||
|
||||
const toggleCollapse = () => {
|
||||
isCollapsed.value = !isCollapsed.value;
|
||||
localStorage.setItem("ctms_sidebar_collapsed", isCollapsed.value ? "1" : "0");
|
||||
@@ -200,6 +359,10 @@ const onLogout = () => {
|
||||
router.replace("/login");
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadStudies();
|
||||
});
|
||||
|
||||
const onCommand = (cmd: string) => {
|
||||
if (cmd === "logout") {
|
||||
onLogout();
|
||||
@@ -216,12 +379,12 @@ const onCommand = (cmd: string) => {
|
||||
}
|
||||
|
||||
.layout-aside {
|
||||
background-color: #ffffff;
|
||||
background-color: #2c3e50;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: none;
|
||||
box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
|
||||
z-index: 10;
|
||||
border-right: 1px solid var(--ctms-border-color);
|
||||
border-right: none;
|
||||
transition: width 0.18s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -249,7 +412,7 @@ const onCommand = (cmd: string) => {
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 24px;
|
||||
padding: 0 16px;
|
||||
background-color: transparent;
|
||||
transition: padding 0.18s ease;
|
||||
}
|
||||
@@ -257,17 +420,17 @@ const onCommand = (cmd: string) => {
|
||||
.logo-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: var(--ctms-primary);
|
||||
border-radius: 10px;
|
||||
background: #ffffff;
|
||||
border-radius: 8px;
|
||||
margin-right: 12px;
|
||||
box-shadow: 0 6px 14px rgba(63, 93, 117, 0.18);
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
color: var(--ctms-text-main);
|
||||
color: #ffffff;
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 1px;
|
||||
letter-spacing: 0.5px;
|
||||
transition: opacity 0.18s ease, transform 0.18s ease;
|
||||
}
|
||||
|
||||
@@ -280,63 +443,68 @@ const onCommand = (cmd: string) => {
|
||||
}
|
||||
|
||||
.menu-divider {
|
||||
padding: 16px 24px 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--ctms-text-secondary);
|
||||
padding: 16px 16px 8px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1.5px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
:deep(.el-menu-item) {
|
||||
.layout-aside :deep(.el-menu-item) {
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
margin: 4px 12px;
|
||||
border-radius: 12px;
|
||||
color: var(--ctms-text-regular) !important;
|
||||
margin: 4px 10px;
|
||||
border-radius: 8px;
|
||||
color: #f1f5f9 !important;
|
||||
transition: var(--ctms-transition);
|
||||
}
|
||||
|
||||
:deep(.el-menu-item:hover) {
|
||||
color: var(--ctms-text-main) !important;
|
||||
background-color: var(--ctms-bg-muted) !important;
|
||||
.layout-aside :deep(.el-menu-item:hover) {
|
||||
color: #ffffff !important;
|
||||
background-color: rgba(255, 255, 255, 0.08) !important;
|
||||
}
|
||||
|
||||
:deep(.el-menu-item.is-active) {
|
||||
color: var(--ctms-primary) !important;
|
||||
background-color: var(--ctms-primary-light) !important;
|
||||
.layout-aside :deep(.el-menu-item.is-active) {
|
||||
color: #ffffff !important;
|
||||
background-color: rgba(255, 255, 255, 0.12) !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
:deep(.el-menu-item.is-active::before) {
|
||||
.layout-aside :deep(.el-menu-item.is-active::before) {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
top: 12px;
|
||||
bottom: 12px;
|
||||
left: 0;
|
||||
top: 10px;
|
||||
bottom: 10px;
|
||||
width: 3px;
|
||||
background-color: var(--ctms-primary);
|
||||
border-radius: 999px;
|
||||
background-color: #3b82f6;
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
:deep(.el-sub-menu__title) {
|
||||
color: var(--ctms-text-regular) !important;
|
||||
margin: 4px 12px;
|
||||
border-radius: 12px;
|
||||
.layout-aside :deep(.el-sub-menu__title) {
|
||||
color: #f1f5f9 !important;
|
||||
margin: 4px 10px;
|
||||
border-radius: 8px;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
}
|
||||
|
||||
:deep(.el-sub-menu.is-active .el-sub-menu__title) {
|
||||
color: var(--ctms-text-main) !important;
|
||||
.layout-aside :deep(.el-sub-menu__title:hover) {
|
||||
color: #ffffff !important;
|
||||
background-color: rgba(255, 255, 255, 0.08) !important;
|
||||
}
|
||||
|
||||
:deep(.el-menu--inline) {
|
||||
.layout-aside :deep(.el-sub-menu.is-active .el-sub-menu__title) {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.layout-aside :deep(.el-menu--inline) {
|
||||
background-color: transparent !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.el-menu--inline .el-menu-item) {
|
||||
.layout-aside :deep(.el-menu--inline .el-menu-item) {
|
||||
padding-left: 48px !important;
|
||||
margin: 2px 12px;
|
||||
}
|
||||
@@ -350,7 +518,53 @@ const onCommand = (cmd: string) => {
|
||||
box-shadow: none;
|
||||
height: 64px !important;
|
||||
z-index: 9;
|
||||
border-bottom: 1px solid var(--ctms-border-color);
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
}
|
||||
|
||||
.header-breadcrumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--ctms-text-regular);
|
||||
font-size: 14px;
|
||||
margin-left: 20px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.breadcrumb-item-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.breadcrumb-item-wrapper.is-link {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.breadcrumb-item-wrapper.is-link:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.is-link-text {
|
||||
color: #3b82f6;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.breadcrumb-item-text {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.breadcrumb-dropdown-icon {
|
||||
font-size: 12px;
|
||||
color: #94a3b8;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.breadcrumb-separator {
|
||||
margin: 0 4px;
|
||||
color: #cbd5e1;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
@@ -422,7 +636,7 @@ const onCommand = (cmd: string) => {
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 28px;
|
||||
padding: 16px;
|
||||
min-height: calc(100vh - 64px);
|
||||
}
|
||||
|
||||
@@ -441,4 +655,13 @@ const onCommand = (cmd: string) => {
|
||||
opacity: 0;
|
||||
transform: translateX(15px);
|
||||
}
|
||||
.breadcrumb-dropdown-menu {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.breadcrumb-dropdown-menu :deep(.el-dropdown-menu__item.active) {
|
||||
color: var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user