UI界面模拟shadcn admin效果
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<el-dialog :title="isEdit ? '编辑 AE' : '新增 AE'" v-model="visible" width="600px" @close="onClose">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
|
||||
<el-dialog :title="isEdit ? '编辑 AE' : '新增 AE'" v-model="visible" width="600px" @close="onClose" class="ctms-dialog">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" class="ctms-form--dense form-body">
|
||||
<el-form-item label="受试者" prop="subject_id">
|
||||
<SubjectSelect v-model="form.subject_id" :study-id="study.currentStudy?.id || null" placeholder="可留空" />
|
||||
</el-form-item>
|
||||
@@ -26,8 +26,10 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onClose">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="onSubmit">提交</el-button>
|
||||
<div class="footer-right">
|
||||
<el-button @click="onClose">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="onSubmit">提交</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
@@ -146,3 +148,21 @@ const onSubmit = async () => {
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__body) {
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__footer) {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<el-dialog :title="isEdit ? '编辑数据问题' : '新建数据问题'" v-model="visible" width="600px" @close="onClose">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
|
||||
<el-dialog :title="isEdit ? '编辑数据问题' : '新建数据问题'" v-model="visible" width="600px" @close="onClose" class="ctms-dialog">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" class="ctms-form--dense form-body">
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input v-model="form.title" />
|
||||
</el-form-item>
|
||||
@@ -33,8 +33,10 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onClose">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="onSubmit">提交</el-button>
|
||||
<div class="footer-right">
|
||||
<el-button @click="onClose">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="onSubmit">提交</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
@@ -142,3 +144,21 @@ const onSubmit = async () => {
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__body) {
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__footer) {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<el-dialog :title="isEdit ? '编辑问题' : '新增问题'" v-model="visible" width="600px" @close="onClose">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
|
||||
<el-dialog :title="isEdit ? '编辑问题' : '新增问题'" v-model="visible" width="600px" @close="onClose" class="ctms-dialog">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" class="ctms-form--dense form-body">
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input v-model="form.title" />
|
||||
</el-form-item>
|
||||
@@ -25,8 +25,10 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onClose">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="onSubmit">提交</el-button>
|
||||
<div class="footer-right">
|
||||
<el-button @click="onClose">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="onSubmit">提交</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
@@ -148,3 +150,21 @@ const levelLabel = (l: string) =>
|
||||
CRITICAL: "关键",
|
||||
}[l] || l);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__body) {
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__footer) {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -37,14 +37,12 @@ defineProps<Props>();
|
||||
<style scoped>
|
||||
.kpi-card {
|
||||
border: 1px solid var(--ctms-border-color);
|
||||
transition: all 0.3s ease;
|
||||
transition: var(--ctms-transition);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.kpi-card:hover {
|
||||
border-color: var(--ctms-primary-hover);
|
||||
box-shadow: var(--ctms-shadow-md) !important;
|
||||
transform: translateY(-2px);
|
||||
border-color: var(--ctms-border-color-hover);
|
||||
}
|
||||
|
||||
.kpi-header {
|
||||
@@ -56,14 +54,13 @@ defineProps<Props>();
|
||||
|
||||
.kpi-title {
|
||||
color: var(--ctms-text-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.kpi-icon {
|
||||
font-size: 18px;
|
||||
color: var(--ctms-primary);
|
||||
opacity: 0.6;
|
||||
font-size: 16px;
|
||||
color: var(--ctms-text-secondary);
|
||||
}
|
||||
|
||||
.kpi-content {
|
||||
@@ -78,8 +75,8 @@ defineProps<Props>();
|
||||
}
|
||||
|
||||
.kpi-value {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: var(--ctms-text-main);
|
||||
line-height: 1.2;
|
||||
}
|
||||
@@ -92,7 +89,7 @@ defineProps<Props>();
|
||||
|
||||
.kpi-subtext {
|
||||
color: var(--ctms-text-secondary);
|
||||
font-size: 13px;
|
||||
font-size: 12px;
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px dashed var(--ctms-border-color);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<el-container class="layout-container">
|
||||
<el-aside width="240px" class="layout-aside" v-if="isAdmin || study.currentStudy">
|
||||
<el-aside :width="isCollapsed ? '72px' : '240px'" class="layout-aside" :class="{ collapsed: isCollapsed }" v-if="isAdmin || study.currentStudy">
|
||||
<div class="aside-logo">
|
||||
<div class="logo-icon"></div>
|
||||
<span class="logo-text">CTMS</span>
|
||||
@@ -8,6 +8,8 @@
|
||||
<el-menu
|
||||
router
|
||||
:default-active="$route.path"
|
||||
:collapse="isCollapsed"
|
||||
:collapse-transition="true"
|
||||
class="aside-menu"
|
||||
>
|
||||
<el-menu-item v-if="!isAdmin" index="/workbench">
|
||||
@@ -34,32 +36,32 @@
|
||||
<span>项目概览</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/study/milestones">
|
||||
<el-icon><Timer /></el-icon>
|
||||
<el-icon><Calendar /></el-icon>
|
||||
<span>伦理与启动</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/study/subjects">
|
||||
<el-icon><UserFilled /></el-icon>
|
||||
<el-icon><User /></el-icon>
|
||||
<span>受试者</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/study/aes">
|
||||
<el-icon><Warning /></el-icon>
|
||||
<el-icon><WarningFilled /></el-icon>
|
||||
<span>不良事件</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/study/issues">
|
||||
<el-icon><InfoFilled /></el-icon>
|
||||
<el-icon><Flag /></el-icon>
|
||||
<span>风险与问题</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/study/data-queries">
|
||||
<el-icon><QuestionFilled /></el-icon>
|
||||
<el-icon><ChatDotRound /></el-icon>
|
||||
<span>数据问题</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/study/verifications">
|
||||
<el-icon><Checked /></el-icon>
|
||||
<el-icon><CircleCheck /></el-icon>
|
||||
<span>数据核查</span>
|
||||
</el-menu-item>
|
||||
<el-sub-menu index="imp">
|
||||
<template #title>
|
||||
<el-icon><Management /></el-icon>
|
||||
<el-icon><Box /></el-icon>
|
||||
<span>药品管理</span>
|
||||
</template>
|
||||
<el-menu-item index="/study/imp/inventory">库存</el-menu-item>
|
||||
@@ -68,15 +70,15 @@
|
||||
<el-menu-item index="/study/imp/transactions">交易台账</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-menu-item index="/study/finance">
|
||||
<el-icon><Money /></el-icon>
|
||||
<el-icon><Coin /></el-icon>
|
||||
<span>费用治理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/study/faq">
|
||||
<el-icon><Collection /></el-icon>
|
||||
<el-icon><Notebook /></el-icon>
|
||||
<span>知识库</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item v-if="isAdmin || isPm" index="/study/audit-logs">
|
||||
<el-icon><Memo /></el-icon>
|
||||
<el-icon><Document /></el-icon>
|
||||
<span>审计日志</span>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
@@ -86,7 +88,14 @@
|
||||
<el-container class="main-container">
|
||||
<el-header class="layout-header">
|
||||
<div class="header-left">
|
||||
<!-- 移除冗余的 study-tag,由 StudySelector 统一展示交互 -->
|
||||
<el-button class="collapse-btn" @click="toggleCollapse" aria-label="收缩侧边栏">
|
||||
<el-icon class="collapse-icon">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<rect x="4" y="5" width="16" height="14" rx="2" fill="none" stroke="currentColor" stroke-width="1.8" />
|
||||
<line x1="9.5" y1="5" x2="9.5" y2="19" stroke="currentColor" stroke-width="1.8" />
|
||||
</svg>
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<StudySelector />
|
||||
@@ -133,15 +142,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useAuthStore } from "../store/auth";
|
||||
import { useStudyStore } from "../store/study";
|
||||
import StudySelector from "./StudySelector.vue";
|
||||
import {
|
||||
Monitor, User, Suitcase, House, Timer, UserFilled,
|
||||
Warning, InfoFilled, QuestionFilled, Checked, Management,
|
||||
Money, Collection, Memo, ArrowDown, SwitchButton
|
||||
Monitor, User, Suitcase, House, Calendar, WarningFilled, Flag, ChatDotRound,
|
||||
CircleCheck, Box, Coin, Notebook, Document, ArrowDown, SwitchButton
|
||||
} from "@element-plus/icons-vue";
|
||||
|
||||
const auth = useAuthStore();
|
||||
@@ -149,6 +157,12 @@ const study = useStudyStore();
|
||||
const router = useRouter();
|
||||
const isAdmin = computed(() => auth.user?.role === "ADMIN");
|
||||
const isPm = computed(() => auth.user?.role === "PM" || study.currentStudyRole === "PM");
|
||||
const isCollapsed = ref(localStorage.getItem("ctms_sidebar_collapsed") === "1");
|
||||
|
||||
const toggleCollapse = () => {
|
||||
isCollapsed.value = !isCollapsed.value;
|
||||
localStorage.setItem("ctms_sidebar_collapsed", isCollapsed.value ? "1" : "0");
|
||||
};
|
||||
|
||||
const onLogout = () => {
|
||||
auth.logout();
|
||||
@@ -168,16 +182,37 @@ const onCommand = (cmd: string) => {
|
||||
<style scoped>
|
||||
.layout-container {
|
||||
height: 100vh;
|
||||
background-color: #f6f8fb;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.layout-aside {
|
||||
background-color: #001529; /* 经典深色,保持专业感 */
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 4px 0 10px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: none;
|
||||
z-index: 10;
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.05);
|
||||
border-right: 1px solid var(--ctms-border-color);
|
||||
transition: width 0.18s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.aside-menu {
|
||||
transition: padding 0.18s ease;
|
||||
}
|
||||
|
||||
.layout-aside.collapsed .logo-text {
|
||||
opacity: 0;
|
||||
transform: translateX(-4px);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.layout-aside.collapsed .aside-logo {
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.layout-aside.collapsed .menu-divider {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.aside-logo {
|
||||
@@ -185,23 +220,25 @@ const onCommand = (cmd: string) => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 24px;
|
||||
background-color: #001529;
|
||||
background-color: transparent;
|
||||
transition: padding 0.18s ease;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: linear-gradient(135deg, var(--ctms-primary) 0%, #6366f1 100%);
|
||||
border-radius: 8px;
|
||||
background: var(--ctms-primary);
|
||||
border-radius: 10px;
|
||||
margin-right: 12px;
|
||||
box-shadow: 0 4px 8px rgba(47, 84, 235, 0.3);
|
||||
box-shadow: 0 6px 14px rgba(63, 93, 117, 0.18);
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
color: var(--ctms-text-main);
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 1px;
|
||||
transition: opacity 0.18s ease, transform 0.18s ease;
|
||||
}
|
||||
|
||||
.aside-menu {
|
||||
@@ -216,7 +253,7 @@ const onCommand = (cmd: string) => {
|
||||
padding: 16px 24px 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.35);
|
||||
color: var(--ctms-text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1.5px;
|
||||
}
|
||||
@@ -225,47 +262,47 @@ const onCommand = (cmd: string) => {
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
margin: 4px 12px;
|
||||
border-radius: var(--ctms-radius);
|
||||
color: rgba(255, 255, 255, 0.7) !important;
|
||||
border-radius: 12px;
|
||||
color: var(--ctms-text-regular) !important;
|
||||
transition: var(--ctms-transition);
|
||||
}
|
||||
|
||||
:deep(.el-menu-item:hover) {
|
||||
color: #fff !important;
|
||||
background-color: rgba(255, 255, 255, 0.05) !important;
|
||||
color: var(--ctms-text-main) !important;
|
||||
background-color: var(--ctms-bg-muted) !important;
|
||||
}
|
||||
|
||||
:deep(.el-menu-item.is-active) {
|
||||
color: #fff !important;
|
||||
background-color: var(--ctms-primary) !important;
|
||||
box-shadow: 0 4px 12px rgba(47, 84, 235, 0.2);
|
||||
color: var(--ctms-primary) !important;
|
||||
background-color: var(--ctms-primary-light) !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
:deep(.el-menu-item.is-active::before) {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -12px;
|
||||
left: 8px;
|
||||
top: 12px;
|
||||
bottom: 12px;
|
||||
width: 4px;
|
||||
background-color: #fff;
|
||||
border-radius: 0 4px 4px 0;
|
||||
width: 3px;
|
||||
background-color: var(--ctms-primary);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
:deep(.el-sub-menu__title) {
|
||||
color: rgba(255, 255, 255, 0.7) !important;
|
||||
color: var(--ctms-text-regular) !important;
|
||||
margin: 4px 12px;
|
||||
border-radius: var(--ctms-radius);
|
||||
border-radius: 12px;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
}
|
||||
|
||||
:deep(.el-sub-menu.is-active .el-sub-menu__title) {
|
||||
color: #fff !important;
|
||||
color: var(--ctms-text-main) !important;
|
||||
}
|
||||
|
||||
:deep(.el-menu--inline) {
|
||||
background-color: rgba(0, 0, 0, 0.2) !important;
|
||||
background-color: transparent !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
@@ -275,14 +312,15 @@ const onCommand = (cmd: string) => {
|
||||
}
|
||||
|
||||
.layout-header {
|
||||
background-color: #fff;
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 24px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.03);
|
||||
box-shadow: none;
|
||||
height: 64px !important;
|
||||
z-index: 9;
|
||||
border-bottom: 1px solid var(--ctms-border-color);
|
||||
}
|
||||
|
||||
.header-right {
|
||||
@@ -291,6 +329,21 @@ const onCommand = (cmd: string) => {
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.collapse-btn {
|
||||
border: 1px solid var(--ctms-border-color);
|
||||
color: var(--ctms-text-regular);
|
||||
}
|
||||
|
||||
.collapse-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
|
||||
.user-profile {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -301,23 +354,23 @@ const onCommand = (cmd: string) => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 12px;
|
||||
border-radius: 14px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid #eef2f6;
|
||||
color: #334155;
|
||||
border-radius: 999px;
|
||||
background: #ffffff;
|
||||
border: 1px solid var(--ctms-border-color);
|
||||
color: var(--ctms-text-regular);
|
||||
transition: var(--ctms-transition);
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.dropdown-trigger.compact:hover {
|
||||
background: #f1f5f9;
|
||||
border-color: #e2e8f0;
|
||||
background: var(--ctms-bg-muted);
|
||||
border-color: var(--ctms-border-color-hover);
|
||||
}
|
||||
|
||||
.user-avatar-comp {
|
||||
color: var(--ctms-primary) !important;
|
||||
background-color: transparent !important;
|
||||
border-radius: 8px !important;
|
||||
border-radius: 10px !important;
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
font-size: 14px;
|
||||
@@ -339,7 +392,7 @@ const onCommand = (cmd: string) => {
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 24px;
|
||||
padding: 28px;
|
||||
min-height: calc(100vh - 64px);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
class="action-item"
|
||||
@click="go(item.path)"
|
||||
>
|
||||
<div class="action-icon-wrapper" :style="{ backgroundColor: item.color + '20', color: item.color }">
|
||||
<div class="action-icon-wrapper">
|
||||
<el-icon><component :is="item.icon" /></el-icon>
|
||||
</div>
|
||||
<span class="action-label">{{ item.label }}</span>
|
||||
@@ -31,13 +31,13 @@ import {
|
||||
const router = useRouter();
|
||||
|
||||
const actions = [
|
||||
{ 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" },
|
||||
{ label: "伦理与启动", path: "/study/milestones", icon: Timer },
|
||||
{ label: "受试者", path: "/study/subjects", icon: UserFilled },
|
||||
{ label: "不良事件", path: "/study/aes", icon: Warning },
|
||||
{ label: "数据问题", path: "/study/data-queries", icon: QuestionFilled },
|
||||
{ label: "药品库存", path: "/study/imp/inventory", icon: Management },
|
||||
{ label: "费用治理", path: "/study/finance", icon: Money },
|
||||
{ label: "项目知识库", path: "/study/faq", icon: Collection },
|
||||
];
|
||||
|
||||
const go = (path: string) => router.push(path);
|
||||
@@ -48,7 +48,7 @@ const go = (path: string) => router.push(path);
|
||||
background-color: #fff;
|
||||
border-radius: var(--ctms-radius);
|
||||
padding: 24px;
|
||||
box-shadow: var(--ctms-shadow-sm);
|
||||
border: 1px solid var(--ctms-border-color);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
@@ -59,12 +59,12 @@ const go = (path: string) => router.push(path);
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
font-size: 18px;
|
||||
color: var(--ctms-primary);
|
||||
font-size: 16px;
|
||||
color: var(--ctms-text-secondary);
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 16px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--ctms-text-main);
|
||||
}
|
||||
@@ -79,31 +79,31 @@ const go = (path: string) => router.push(path);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
background-color: #fafafa;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid var(--ctms-border-color);
|
||||
border-radius: 8px;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
transition: var(--ctms-transition);
|
||||
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);
|
||||
background-color: #ffffff;
|
||||
border-color: var(--ctms-border-color-hover);
|
||||
}
|
||||
|
||||
.action-icon-wrapper {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
font-size: 18px;
|
||||
margin-right: 12px;
|
||||
color: var(--ctms-text-secondary);
|
||||
background-color: var(--ctms-bg-muted);
|
||||
}
|
||||
|
||||
.action-label {
|
||||
@@ -116,13 +116,11 @@ const go = (path: string) => router.push(path);
|
||||
.action-arrow {
|
||||
font-size: 14px;
|
||||
color: var(--ctms-text-disabled);
|
||||
opacity: 0;
|
||||
transition: all 0.3s ease;
|
||||
transform: translateX(-10px);
|
||||
opacity: 1;
|
||||
transition: var(--ctms-transition);
|
||||
}
|
||||
|
||||
.action-item:hover .action-arrow {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
color: var(--ctms-text-secondary);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div class="state state-empty">
|
||||
<el-icon class="state-icon"><Document /></el-icon>
|
||||
<div class="state-title">{{ title }}</div>
|
||||
<div v-if="description" class="state-desc">{{ description }}</div>
|
||||
<div v-if="$slots.action" class="state-action">
|
||||
<slot name="action" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Document } from "@element-plus/icons-vue";
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
title?: string;
|
||||
description?: string;
|
||||
}>(),
|
||||
{
|
||||
title: "暂无数据",
|
||||
description: "",
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px 12px;
|
||||
color: var(--ctms-text-secondary);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.state-icon {
|
||||
font-size: 36px;
|
||||
color: var(--ctms-text-disabled);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.state-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--ctms-text-regular);
|
||||
}
|
||||
|
||||
.state-desc {
|
||||
font-size: 12px;
|
||||
color: var(--ctms-text-secondary);
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.state-action {
|
||||
margin-top: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div class="state state-error">
|
||||
<el-icon class="state-icon"><CircleCloseFilled /></el-icon>
|
||||
<div class="state-title">{{ title }}</div>
|
||||
<div v-if="description" class="state-desc">{{ description }}</div>
|
||||
<div v-if="$slots.action" class="state-action">
|
||||
<slot name="action" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { CircleCloseFilled } from "@element-plus/icons-vue";
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
title?: string;
|
||||
description?: string;
|
||||
}>(),
|
||||
{
|
||||
title: "加载失败",
|
||||
description: "请稍后重试",
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.state-icon {
|
||||
font-size: 36px;
|
||||
color: var(--ctms-danger);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.state-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--ctms-text-main);
|
||||
}
|
||||
|
||||
.state-desc {
|
||||
font-size: 12px;
|
||||
color: var(--ctms-text-secondary);
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.state-action {
|
||||
margin-top: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div class="state state-loading">
|
||||
<el-skeleton :rows="rows" animated />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
rows?: number;
|
||||
}>(),
|
||||
{
|
||||
rows: 4,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.state-loading {
|
||||
padding: 24px;
|
||||
border: 1px solid var(--ctms-border-color);
|
||||
border-radius: var(--ctms-radius);
|
||||
background-color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<el-dialog title="新增受试者" v-model="visible" width="520px" @close="onClose">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-dialog title="新增受试者" v-model="visible" width="520px" @close="onClose" class="ctms-dialog">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" class="ctms-form--dense form-body">
|
||||
<el-form-item label="中心" prop="site_id">
|
||||
<el-select v-model="form.site_id" placeholder="请选择" filterable>
|
||||
<el-option v-for="s in sites" :key="s.id" :label="s.name || s.id" :value="s.id" />
|
||||
@@ -14,9 +14,13 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-checkbox v-model="keepCreating" class="mr-8">提交后继续新建</el-checkbox>
|
||||
<el-button @click="onClose">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="onSubmit">提交</el-button>
|
||||
<div class="footer-left">
|
||||
<el-checkbox v-model="keepCreating">提交后继续新建</el-checkbox>
|
||||
</div>
|
||||
<div class="footer-right">
|
||||
<el-button @click="onClose">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="onSubmit">提交</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
@@ -100,3 +104,25 @@ const onSubmit = async () => {
|
||||
|
||||
resetForm();
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.form-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__body) {
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__footer) {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer-left {
|
||||
color: var(--ctms-text-secondary);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user