---
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 根容器设置

```vue
<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 结构

```vue
<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 面包屑导航设计

```vue
<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 搜索框设计

```vue
<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 滚动容器

```vue
<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 自定义滚动条样式

```css
.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 通用列表结构

```vue
<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 列表项悬停效果

```css
.v-list-item {
  transition: all 0.2s ease;
}

.v-list-item:hover {
  background-color: rgba(var(--v-theme-primary), 0.08);
}
```

---

## 5. 数据表格设计模式

### 5.1 表格工具栏

```vue
<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 数据表格

```vue
<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 状态标签设计

```vue
<v-chip :color="getStatusColor(status)" size="small" variant="flat">
  <v-icon start size="x-small">{{ getStatusIcon(status) }}</v-icon>
  {{ status }}
</v-chip>
```

**状态颜色映射：**
```typescript
const statusColors = {
  'SUCCESS': 'success',
  'FAILURE': 'error',
  'UNSTABLE': 'warning',
  'ABORTED': 'grey',
  'RUNNING': 'info'
}
```

---

## 6. 详情页面设计模式

### 6.1 详情页头部

```vue
<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 详情卡片分组

```vue
<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)

```vue
<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：**
```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 标准弹窗

```vue
<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 全屏弹窗（日志查看器）

```vue
<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 加载状态

```vue
<div v-if="loading" class="pa-4">
  <v-skeleton-loader type="list-item@5" />
</div>
```

### 8.2 错误状态

```vue
<v-alert 
  v-else-if="error" 
  type="error" 
  variant="tonal" 
  class="ma-4" 
  closable 
  @click:close="error = null"
>
  {{ error }}
</v-alert>
```

### 8.3 空状态

```vue
<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 组件拆分原则

1. **单一职责**：每个组件只负责一个功能
2. **可复用性**：将通用 UI 模式抽取为独立组件
3. **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秒间隔（仅运行中状态）

---

## 使用示例

### 创建新的钻取式页面

```vue
<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>
```

---
