17 KiB
17 KiB
description
| description |
|---|
| UserDashboard 页面样式与布局设计指南 - 供后续页面设计参考 |
UserDashboard 页面样式与布局设计指南
概述
此文档描述了 UserDashboard.vue 页面的样式和布局设计模式,可作为后续页面设计的参考标准。该页面采用 Vue 3 + Vuetify 3 + TypeScript 技术栈,实现了一个分层钻取式(Drill-Down)导航的 Dashboard 界面。
1. 整体页面结构
1.1 布局架构
┌─────────────────────────────────────────────────────────────┐
│ 顶部工具栏 (Top Bar) │
│ - 面包屑导航 (Breadcrumbs) │
│ - 全局搜索框 (Search Autocomplete) │
├─────────────────────────────────────────────────────────────┤
│ │
│ 主内容区域 (Main Content Area) │
│ - 可滚动区域 │
│ - 根据导航层级动态切换内容组件 │
│ │
│ │
│ │
└─────────────────────────────────────────────────────────────┘
1.2 根容器设置
<v-container
fluid
class="d-flex flex-column h-100 pa-0"
style="width: 95%; height: 100vh; min-height: 0;"
>
设计要点:
- 使用
fluid流式布局 d-flex flex-column实现垂直弹性布局h-100占满高度pa-0移除内边距- 宽度
95%留出两侧适当边距
2. 顶部工具栏设计
2.1 结构
<div class="d-flex align-center px-4 py-2 bg-white border-b flex-shrink-0">
<!-- 面包屑导航 -->
<DashboardBreadcrumbs class="flex-grow-1" ... />
<!-- 搜索框 -->
<div style="width: 350px" class="ml-4">
<v-autocomplete ... />
</div>
</div>
2.2 设计要点
- 固定高度:使用
flex-shrink-0防止压缩 - 背景色:
bg-white白色背景 - 底部边框:
border-b分隔线 - 内边距:
px-4 py-2水平16px,垂直8px - 内容对齐:
d-flex align-center水平排列垂直居中
2.3 面包屑导航设计
<div class="px-4 py-2 d-flex align-center">
<v-btn variant="text" @click="..." :disabled="...">
层级名称
</v-btn>
<v-icon v-if="..." size="small" class="mx-2">mdi-chevron-right</v-icon>
<!-- 更多层级... -->
</div>
特点:
- 使用
v-btn variant="text"作为可点击导航项 - 使用
mdi-chevron-right图标作为分隔符 - 图标尺寸
size="small",间距mx-2 - 当前层级设置
:disabled="true"表示不可点击
2.4 搜索框设计
<v-autocomplete
v-model="treeSearchSelect"
v-model:search="treeSearchQuery"
:items="treeSearchResults"
:loading="treeSearchLoading"
item-title="name"
item-value="id"
placeholder="Search Org / Repo / Branch..."
variant="outlined"
density="compact"
hide-details
prepend-inner-icon="mdi-magnify"
return-object
hide-no-data
bg-color="grey-lighten-5"
clearable
>
特点:
variant="outlined"外框样式density="compact"紧凑型高度bg-color="grey-lighten-5"浅灰色背景prepend-inner-icon="mdi-magnify"搜索图标hide-details隐藏辅助文本空间clearable可清除按钮
3. 主内容区域设计
3.1 滚动容器
<div class="flex-grow-1 overflow-y-auto custom-scrollbar"
style="height: 80vh; min-height: 0; overflow-y: auto; overflow-x: hidden;">
设计要点:
flex-grow-1填充剩余空间overflow-y-auto垂直滚动overflow-x-hidden隐藏水平滚动- 自定义滚动条样式
3.2 自定义滚动条样式
.custom-scrollbar {
scrollbar-width: thin;
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
}
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: transparent;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.2);
border-radius: 3px;
}
特点:
- 细滚动条
width: 6px - 透明轨道
- 半透明滚动条滑块
- 圆角处理
4. 列表组件设计模式
4.1 通用列表结构
<v-list lines="two" class="pa-2">
<v-list-subheader class="text-h6 font-weight-medium">
<v-icon class="mr-2">图标</v-icon>
列表标题
</v-list-subheader>
<!-- 空状态 -->
<v-empty-state
v-if="items.length === 0"
icon="mdi-database-off-outline"
title="暂无数据"
text="描述文本"
/>
<!-- 列表项 -->
<v-list-item
v-for="item in items"
:key="item.id"
@click="..."
class="mb-2"
rounded
hover
>
<!-- 前置图标 -->
<template #prepend>
<v-avatar color="primary" size="40">
<v-icon>图标</v-icon>
</v-avatar>
</template>
<!-- 标题 -->
<v-list-item-title class="font-weight-medium">
{{ item.name }}
</v-list-item-title>
<!-- 副标题 -->
<v-list-item-subtitle>...</v-list-item-subtitle>
<!-- 后置图标 -->
<template #append>
<v-icon>mdi-chevron-right</v-icon>
</template>
</v-list-item>
</v-list>
4.2 列表项悬停效果
.v-list-item {
transition: all 0.2s ease;
}
.v-list-item:hover {
background-color: rgba(var(--v-theme-primary), 0.08);
}
5. 数据表格设计模式
5.1 表格工具栏
<v-toolbar color="transparent" density="compact" class="px-4">
<v-icon class="mr-2">mdi-hammer-wrench</v-icon>
<v-toolbar-title class="text-h6">表格标题</v-toolbar-title>
<v-spacer />
<v-btn color="primary" prepend-icon="mdi-plus" class="mr-2">
操作按钮
</v-btn>
<v-btn icon="mdi-refresh" variant="text" @click="..." :loading="loading" />
</v-toolbar>
5.2 数据表格
<v-data-table
:headers="headers"
:items="items"
:items-per-page="50"
:items-per-page-options="[25, 50, 100]"
fixed-header
height="calc(100vh - 280px)"
class="elevation-1"
hover
@click:row="handleRowClick"
>
<!-- 自定义列模板 -->
</v-data-table>
5.3 状态标签设计
<v-chip :color="getStatusColor(status)" size="small" variant="flat">
<v-icon start size="x-small">{{ getStatusIcon(status) }}</v-icon>
{{ status }}
</v-chip>
状态颜色映射:
const statusColors = {
'SUCCESS': 'success',
'FAILURE': 'error',
'UNSTABLE': 'warning',
'ABORTED': 'grey',
'RUNNING': 'info'
}
6. 详情页面设计模式
6.1 详情页头部
<div class="d-flex align-center justify-space-between pa-4 bg-surface w-100">
<!-- 左侧组 -->
<div class="d-flex align-center ga-3">
<v-btn icon="mdi-arrow-left" variant="text" @click="goBack" />
<span class="text-h6 font-weight-bold">标题</span>
<v-chip :color="statusColor" size="small" variant="flat">状态</v-chip>
</div>
<!-- 右侧组 -->
<div class="d-flex align-center ga-2">
<v-btn color="info" variant="tonal" prepend-icon="mdi-eye">操作1</v-btn>
<v-btn color="primary" variant="flat" prepend-icon="mdi-open-in-new">操作2</v-btn>
</div>
</div>
6.2 详情卡片分组
<v-row>
<!-- 左侧列:主要信息 -->
<v-col cols="12" md="7">
<v-card class="mb-4" variant="outlined">
<v-card-title class="text-subtitle-1 font-weight-bold">
<v-icon start size="small" color="primary">mdi-icon</v-icon>
卡片标题
</v-card-title>
<v-divider />
<v-card-text class="pa-4">
<!-- 详情内容 -->
</v-card-text>
</v-card>
</v-col>
<!-- 右侧列:元信息 -->
<v-col cols="12" md="5">
<!-- 更多卡片... -->
</v-col>
</v-row>
6.3 详情项组件 (DetailItem)
<div class="d-flex align-center mb-5 detail-item">
<!-- 标签区域:固定宽度 -->
<div class="d-flex align-center text-medium-emphasis flex-shrink-0" style="width: 140px;">
<v-icon size="small" class="mr-2" color="primary">{{ icon }}</v-icon>
<span class="text-body-2 font-weight-medium">{{ label }}</span>
</div>
<!-- 值区域:胶囊样式 -->
<div class="flex-grow-1 d-flex align-center justify-space-between px-4 py-2 rounded-pill bg-grey-lighten-5 value-capsule">
<div class="text-body-2 font-weight-medium text-truncate flex-grow-1 mr-2">
<slot name="value">{{ value }}</slot>
</div>
<v-btn v-if="copyable" icon="mdi-content-copy" variant="text" size="x-small"
color="medium-emphasis" class="copy-btn" @click="copy" />
</div>
</div>
胶囊样式 CSS:
.value-capsule {
transition: all 0.2s ease;
min-height: 44px;
border: 1px solid rgba(0, 0, 0, 0.05);
}
.value-capsule:hover {
background-color: rgba(var(--v-theme-on-surface), 0.08) !important;
border-color: rgba(0, 0, 0, 0.1);
}
.copy-btn {
opacity: 0;
transition: opacity 0.2s ease;
}
.value-capsule:hover .copy-btn {
opacity: 1;
}
7. 弹窗设计模式
7.1 标准弹窗
<v-dialog v-model="showDialog" max-width="60vh" max-height="80vh">
<v-card>
<v-card-title>弹窗标题</v-card-title>
<v-divider />
<v-card-text>内容</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn variant="text" @click="cancel">取消</v-btn>
<v-btn color="primary" @click="confirm">确认</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
7.2 全屏弹窗(日志查看器)
<v-dialog v-model="showDialog" fullscreen transition="dialog-bottom-transition">
<v-card>
<v-toolbar color="primary" density="compact">
<v-btn icon="mdi-close" @click="close"></v-btn>
<v-toolbar-title>全屏弹窗标题</v-toolbar-title>
</v-toolbar>
<v-card-text class="pa-0 h-100">
<!-- 内容 -->
</v-card-text>
</v-card>
</v-dialog>
8. 状态处理模式
8.1 加载状态
<div v-if="loading" class="pa-4">
<v-skeleton-loader type="list-item@5" />
</div>
8.2 错误状态
<v-alert
v-else-if="error"
type="error"
variant="tonal"
class="ma-4"
closable
@click:close="error = null"
>
{{ error }}
</v-alert>
8.3 空状态
<v-empty-state
icon="mdi-database-off-outline"
title="暂无数据"
text="描述性文本说明"
/>
9. 颜色规范
9.1 主题颜色使用
| 用途 | 颜色 | Vuetify类/变量 |
|---|---|---|
| 主要操作 | 蓝色 | primary |
| 次要操作 | 灰色 | secondary |
| 成功状态 | 绿色 | success |
| 错误状态 | 红色 | error |
| 警告状态 | 橙色 | warning |
| 提示信息 | 蓝色 | info |
| 禁用/中性 | 灰色 | grey |
9.2 背景颜色
- 页面背景:默认(白色/浅灰)
- 卡片背景:
bg-surface - 输入框背景:
bg-grey-lighten-5 - 胶囊值背景:
bg-grey-lighten-5
10. 间距规范
10.1 Vuetify 间距系统
| 值 | 像素 | 使用场景 |
|---|---|---|
| 1 | 4px | 极小间距 |
| 2 | 8px | 小间距 |
| 3 | 12px | 中等间距 |
| 4 | 16px | 标准间距 |
| 5 | 24px | 大间距 |
| 6 | 32px | 超大间距 |
10.2 常用间距组合
- 卡片内边距:
pa-4(16px) - 列表项底部间距:
mb-2(8px) - 详情项底部间距:
mb-5(24px) - 按钮组间距:
ga-2或ga-3
11. 图标使用规范
11.1 常用图标
| 用途 | 图标 |
|---|---|
| 组织 | mdi-domain / mdi-folder-network |
| 仓库 | mdi-source-repository |
| 分支 | mdi-source-branch |
| 构建 | mdi-hammer-wrench |
| 搜索 | mdi-magnify |
| 导航 | mdi-chevron-right / mdi-arrow-left |
| 刷新 | mdi-refresh |
| 关闭 | mdi-close |
| 复制 | mdi-content-copy |
| 外部链接 | mdi-open-in-new |
| 日志 | mdi-text-box-outline |
11.2 图标尺寸
- 列表项图标:
size="small"或默认 - 状态标签图标:
size="x-small" - 头像图标:默认(在
v-avatar内) - 面包屑分隔符:
size="small"
12. 组件化设计原则
12.1 组件拆分原则
- 单一职责:每个组件只负责一个功能
- 可复用性:将通用 UI 模式抽取为独立组件
- Props 驱动:通过 Props 传递数据,通过 Events 传递交互
12.2 本项目组件结构
components/
├── projects/
│ ├── DashboardBreadcrumbs.vue # 面包屑导航
│ ├── OrganizationList.vue # 组织列表
│ ├── RepositoryList.vue # 仓库列表
│ ├── BranchList.vue # 分支列表
│ ├── BuildList.vue # 构建列表
│ └── BuildDetails.vue # 构建详情
├── common/
│ └── DetailItem.vue # 详情项组件
└── jenkins/
├── BuildTrigger.vue # 构建触发器
└── LogViewer.vue # 日志查看器
13. 交互模式
13.1 导航模式
- 钻取式导航:点击列表项进入下一层级
- 面包屑回溯:点击面包屑返回上层
- 全局搜索:快速跳转到任意层级
13.2 动画效果
- 过渡动画:
transition: all 0.2s ease - 弹窗动画:
transition="dialog-bottom-transition" - 悬停效果:背景色变化、元素显现
13.3 自动刷新
- 列表数据刷新:10秒间隔(有运行中任务时3秒)
- 详情数据刷新:3秒间隔(仅运行中状态)
使用示例
创建新的钻取式页面
<template>
<v-container fluid class="d-flex flex-column h-100 pa-0" style="width: 95%; height: 100vh; min-height: 0;">
<!-- 顶部栏 -->
<div class="d-flex align-center px-4 py-2 bg-white border-b flex-shrink-0">
<YourBreadcrumbs ... />
<div style="width: 350px" class="ml-4">
<v-autocomplete ... />
</div>
</div>
<v-divider />
<!-- 主内容区 -->
<div class="flex-grow-1 overflow-y-auto custom-scrollbar" style="height: 80vh; min-height: 0;">
<div v-if="loading" class="pa-4">
<v-skeleton-loader type="list-item@5" />
</div>
<v-alert v-else-if="error" type="error" variant="tonal" class="ma-4" closable>
{{ error }}
</v-alert>
<YourList1 v-else-if="currentLevel === 'level1'" ... />
<YourList2 v-else-if="currentLevel === 'level2'" ... />
<YourDetails v-else-if="currentLevel === 'details'" ... />
</div>
</v-container>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue'
// Navigation State
type NavigationLevel = 'level1' | 'level2' | 'details'
const currentLevel = ref<NavigationLevel>('level1')
const selectedItem1 = ref<string>('')
const selectedItem2 = ref<string>('')
// Data State
const items1 = ref<Item1[]>([])
const items2 = ref<Item2[]>([])
const details = ref<Details | null>(null)
// UI State
const loading = ref(false)
const error = ref<string | null>(null)
// Auto-refresh timer
const refreshTimer = ref<number | null>(null)
// Navigation Functions
function selectItem1(name: string) {
selectedItem1.value = name
currentLevel.value = 'level2'
loadItems2(name)
}
function selectItem2(name: string) {
selectedItem2.value = name
currentLevel.value = 'details'
loadDetails(selectedItem1.value, name)
}
function navigateToLevel1() {
clearTimer()
currentLevel.value = 'level1'
selectedItem1.value = ''
selectedItem2.value = ''
}
function navigateToLevel2() {
clearTimer()
currentLevel.value = 'level2'
selectedItem2.value = ''
}
// Timer Management
function startRefresh() {
stopRefresh()
refreshTimer.value = window.setInterval(() => {
// Refresh logic
}, 10000)
}
function stopRefresh() {
if (refreshTimer.value) {
clearInterval(refreshTimer.value)
refreshTimer.value = null
}
}
function clearTimer() {
stopRefresh()
}
// Lifecycle
onMounted(() => {
loadItems1()
})
onBeforeUnmount(() => {
clearTimer()
})
</script>
<style scoped>
.custom-scrollbar {
scrollbar-width: thin;
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
}
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: transparent;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.2);
border-radius: 3px;
}
</style>