Files
aue/docs/superpowers/plans/2026-05-14-frontend-white-system-refactor.md
2026-06-03 13:16:30 +08:00

1116 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 前端系统性重构 - 白色体系设计规范实施
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 基于前端优化方案与设计规范文档,对前端项目进行系统性重构,统一白色色彩体系,创建通用组件库,优化信息密度,提升用户体验。
**Architecture:** 采用分层重构策略先建立设计令牌系统CSS变量→ 创建通用组件库 → 重构全局样式 → 重构导航系统 → 逐个模块替换为通用组件。确保每一步都可独立验证,保持功能完整性。
**Tech Stack:** Vue 3 (Composition API / `<script setup>`), Vite, Ant Design Vue 4, CSS Variables
---
## 文件结构总览
### 新建文件
| 文件路径 | 职责 |
|---------|------|
| `src/design-tokens.css` | 全局CSS设计令牌颜色、间距、字体、圆角、阴影 |
| `src/components/common/BasePage.vue` | 统一页面容器组件 |
| `src/components/common/PageHeader.vue` | 统一页面标题栏组件 |
| `src/components/common/TabsBar.vue` | 统一标签栏组件 |
| `src/components/common/ContentCard.vue` | 统一内容卡片组件 |
| `src/components/common/StatsRow.vue` | 统计卡片行组件 |
| `src/components/common/DataTable.vue` | 统一数据表格组件 |
### 修改文件
| 文件路径 | 修改内容 |
|---------|---------|
| `src/main.js` | 引入 design-tokens.css |
| `src/style.css` | 重写为白色体系,移除彩色渐变,应用紧凑尺寸 |
| `src/index.css` | 简化为基础重置样式 |
| `src/views/Home.vue` | 导航从下拉框改为顶部标签栏 |
| `src/components/ReadModule.vue` | 使用通用组件替换重复结构 |
| `src/components/ExamModule.vue` | 使用通用组件替换重复结构 |
| `src/components/ManageModule.vue` | 使用通用组件替换重复结构 |
| `src/components/QAModule.vue` | 使用通用组件替换重复结构 |
| `src/components/MindModule.vue` | 使用通用组件替换重复结构 |
| `src/components/DashboardModule.vue` | 使用通用组件替换重复结构 |
| `src/components/PermissionModule.vue` | 使用通用组件替换重复结构 |
---
### Task 1: 创建设计令牌系统 (design-tokens.css)
**Files:**
- Create: `src/design-tokens.css`
- Modify: `src/main.js`
- [ ] **Step 1: 创建 design-tokens.css 文件**
```css
/* ========================================
设计令牌系统 - 白色体系
基于:前端优化方案与设计规范文档
======================================== */
:root {
/* ===== 背景色(白→灰度层级)===== */
--bg-page: #FFFFFF;
--bg-container: #F8F9FA;
--bg-card: #FFFFFF;
--bg-hover: #F1F3F5;
--bg-active: #E9ECEF;
--bg-disabled: #F8F9FA;
--bg-header: #FFFFFF;
--bg-sidebar: #F8F9FA;
/* ===== 边框色 ===== */
--border-light: #E9ECEF;
--border-default: #DEE2E6;
--border-medium: #CED4DA;
--border-dark: #ADB5BD;
/* ===== 文字色(灰度层级)===== */
--text-primary: #212529;
--text-secondary: #495057;
--text-tertiary: #6C757D;
--text-disabled: #ADB5BD;
--text-inverse: #FFFFFF;
/* ===== 功能色(仅状态标识)===== */
--color-success: #2B8A3E;
--color-warning: #E67700;
--color-error: #C92A2A;
--color-info: #1C7ED6;
/* ===== 功能色浅底 ===== */
--bg-success: #EBFBEE;
--bg-warning: #FFF9DB;
--bg-error: #FFF5F5;
--bg-info: #E7F5FF;
/* ===== 主色调(品牌色)===== */
--color-primary: #212529;
--color-primary-hover: #495057;
--color-primary-active: #000000;
--color-link: #1C7ED6;
/* ===== 阴影层级 ===== */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04);
--shadow-md: 0 2px 8px rgba(0, 0, 0, 0.06);
--shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.08);
--shadow-xl: 0 8px 32px rgba(0, 0, 0, 0.10);
/* ===== 间距系统 ===== */
--space-xs: 4px;
--space-sm: 8px;
--space-md: 12px;
--space-lg: 16px;
--space-xl: 20px;
--space-2xl: 24px;
--space-3xl: 32px;
/* ===== 圆角系统 ===== */
--radius-sm: 4px;
--radius-md: 6px;
--radius-lg: 8px;
--radius-xl: 12px;
/* ===== 字体大小 ===== */
--font-xs: 11px;
--font-sm: 12px;
--font-md: 13px;
--font-lg: 14px;
--font-xl: 16px;
--font-2xl: 18px;
--font-3xl: 22px;
/* ===== 字体粗细 ===== */
--weight-normal: 400;
--weight-medium: 500;
--weight-semibold: 600;
--weight-bold: 700;
/* ===== 组件尺寸 ===== */
--header-height: 48px;
--page-header-height: 56px;
--stat-card-height: 72px;
--card-padding: 16px;
--table-row-height: 44px;
--form-gap: 16px;
--tab-btn-padding-v: 6px;
--tab-btn-padding-h: 14px;
--input-height: 36px;
/* ===== 过渡动画 ===== */
--transition-fast: 0.15s ease;
--transition-normal: 0.2s ease;
/* ===== 层级 ===== */
--z-dropdown: 1000;
--z-modal: 1050;
--z-tooltip: 1100;
}
```
- [ ] **Step 2: 在 main.js 中引入 design-tokens.css**
`src/main.js` 中,在 `'./index.css'` 之后添加:
```javascript
import './design-tokens.css'
```
---
### Task 2: 创建通用组件库
#### Task 2.1: 创建 PageHeader 组件
**Files:**
- Create: `src/components/common/PageHeader.vue`
- [ ] **Step 1: 创建 PageHeader.vue**
```vue
<template>
<div class="page-header">
<div class="header-left">
<h1 v-if="title" class="page-title">{{ title }}</h1>
<p v-if="description" class="page-desc">{{ description }}</p>
</div>
<div v-if="$slots.actions" class="header-right">
<slot name="actions" />
</div>
</div>
</template>
<script setup>
defineProps({
title: { type: String, default: '' },
description: { type: String, default: '' }
})
</script>
<style scoped>
.page-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-md) var(--space-xl);
background: var(--bg-header);
border-bottom: 1px solid var(--border-light);
min-height: var(--page-header-height);
}
.header-left {
display: flex;
flex-direction: column;
gap: var(--space-xs);
}
.header-right {
display: flex;
align-items: center;
gap: var(--space-sm);
}
.page-title {
font-size: var(--font-xl);
font-weight: var(--weight-semibold);
color: var(--text-primary);
margin: 0;
line-height: 1.4;
}
.page-desc {
font-size: var(--font-sm);
color: var(--text-tertiary);
margin: 0;
}
</style>
```
#### Task 2.2: 创建 TabsBar 组件
**Files:**
- Create: `src/components/common/TabsBar.vue`
- [ ] **Step 1: 创建 TabsBar.vue**
```vue
<template>
<div class="tabs-bar">
<button
v-for="tab in tabs"
:key="tab.key"
:class="['tab-btn', { active: modelValue === tab.key }]"
@click="$emit('update:modelValue', tab.key)"
>
<span v-if="tab.icon" class="tab-icon" v-html="tab.icon"></span>
<span class="tab-text">{{ tab.label }}</span>
</button>
</div>
</template>
<script setup>
defineProps({
tabs: { type: Array, required: true, default: () => [] },
modelValue: { type: String, default: '' }
})
defineEmits(['update:modelValue'])
</script>
<style scoped>
.tabs-bar {
display: flex;
gap: var(--space-xs);
padding: var(--space-sm) var(--space-lg);
background: var(--bg-header);
border-bottom: 1px solid var(--border-light);
overflow-x: auto;
}
.tab-btn {
display: inline-flex;
align-items: center;
gap: var(--space-sm);
padding: var(--tab-btn-padding-v) var(--tab-btn-padding-h);
border: none;
border-radius: var(--radius-md);
background: transparent;
color: var(--text-secondary);
font-size: var(--font-md);
font-weight: var(--weight-medium);
cursor: pointer;
transition: all var(--transition-fast);
white-space: nowrap;
flex-shrink: 0;
}
.tab-btn:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.tab-btn.active {
background: var(--bg-active);
color: var(--text-primary);
font-weight: var(--weight-semibold);
}
.tab-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
font-size: 14px;
}
.tab-text {
line-height: 1;
}
</style>
```
#### Task 2.3: 创建 ContentCard 组件
**Files:**
- Create: `src/components/common/ContentCard.vue`
- [ ] **Step 1: 创建 ContentCard.vue**
```vue
<template>
<div class="content-card" :class="{ 'has-header': $slots.header }">
<div v-if="$slots.header" class="card-header">
<slot name="header" />
</div>
<div class="card-body" :style="{ padding }">
<slot />
</div>
</div>
</template>
<script setup>
defineProps({
padding: { type: String, default: 'var(--card-padding)' }
})
</script>
<style scoped>
.content-card {
background: var(--bg-card);
border: 1px solid var(--border-light);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
overflow: hidden;
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-md) var(--card-padding);
border-bottom: 1px solid var(--border-light);
gap: var(--space-md);
}
.card-body {
padding: var(--card-padding);
}
</style>
```
#### Task 2.4: 创建 StatsRow 组件
**Files:**
- Create: `src/components/common/StatsRow.vue`
- [ ] **Step 1: 创建 StatsRow.vue**
```vue
<template>
<div class="stats-row">
<div v-for="(stat, index) in stats" :key="index" class="stat-card">
<span class="stat-value">{{ stat.value }}</span>
<span class="stat-label">{{ stat.label }}</span>
</div>
</div>
</template>
<script setup>
defineProps({
stats: { type: Array, required: true, default: () => [] }
})
</script>
<style scoped>
.stats-row {
display: flex;
gap: var(--space-md);
padding: var(--space-md) var(--space-xl);
background: var(--bg-header);
border-bottom: 1px solid var(--border-light);
}
.stat-card {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--space-xs);
padding: var(--space-sm) var(--space-md);
background: var(--bg-container);
border-radius: var(--radius-md);
border: 1px solid var(--border-light);
min-height: var(--stat-card-height);
}
.stat-value {
font-size: var(--font-3xl);
font-weight: var(--weight-bold);
color: var(--text-primary);
line-height: 1.2;
}
.stat-label {
font-size: var(--font-sm);
color: var(--text-tertiary);
font-weight: var(--weight-medium);
}
</style>
```
#### Task 2.5: 创建 DataTable 组件
**Files:**
- Create: `src/components/common/DataTable.vue`
- [ ] **Step 1: 创建 DataTable.vue**
```vue
<template>
<a-table
:dataSource="dataSource"
:columns="columns"
:loading="loading"
:pagination="pagination"
:rowKey="rowKey"
size="small"
:scroll="{ x: scrollX, y: scrollY }"
:locale="emptyLocale"
class="data-table"
@change="handleTableChange"
>
<template v-for="slot in Object.keys($slots)" #[slot]="scope">
<slot :name="slot" v-bind="scope" />
</template>
</a-table>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
dataSource: { type: Array, default: () => [] },
columns: { type: Array, default: () => [] },
loading: { type: Boolean, default: false },
pagination: { type: [Object, Boolean], default: true },
rowKey: { type: [String, Function], default: 'id' },
scrollX: { type: [Number, String], default: undefined },
scrollY: { type: [Number, String], default: undefined },
emptyText: { type: String, default: '暂无数据' }
})
const emit = defineEmits(['change'])
const emptyLocale = computed(() => ({
emptyText: props.emptyText
}))
const handleTableChange = (pagination, filters, sorter) => {
emit('change', { pagination, filters, sorter })
}
</script>
<style scoped>
.data-table {
width: 100%;
}
.data-table :deep(.ant-table-thead > tr > th) {
background: var(--bg-container);
color: var(--text-primary);
font-weight: var(--weight-semibold);
font-size: var(--font-md);
padding: var(--space-sm) var(--space-md);
border-bottom: 1px solid var(--border-default);
}
.data-table :deep(.ant-table-tbody > tr > td) {
font-size: var(--font-md);
padding: var(--space-sm) var(--space-md);
border-bottom: 1px solid var(--border-light);
}
.data-table :deep(.ant-table-tbody > tr:hover > td) {
background: var(--bg-hover);
}
.data-table :deep(.ant-pagination) {
margin-top: var(--space-md);
}
</style>
```
---
### Task 3: 重构全局样式 (style.css)
**Files:**
- Modify: `src/style.css`
- [ ] **Step 1: 完全重写 style.css 为白色体系**
将现有的 `src/style.css` 替换为以下内容:
```css
/* ========================================
全局样式 - 白色体系
基于设计令牌系统
======================================== */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
overflow-x: hidden;
overflow-y: hidden;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'PingFang SC', 'Microsoft YaHei', sans-serif;
line-height: 1.5;
color: var(--text-primary);
background: var(--bg-page);
}
#app {
width: 100%;
height: 100%;
}
/* ===== 布局基础 ===== */
.ant-layout {
background: var(--bg-page);
}
.ant-layout-header {
background: var(--bg-header);
box-shadow: var(--shadow-sm);
}
/* ===== 卡片统一样式 ===== */
.ant-card {
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
border: 1px solid var(--border-light);
transition: all var(--transition-fast);
}
.ant-card:hover {
box-shadow: var(--shadow-md);
}
.ant-card-head {
border-bottom: 1px solid var(--border-light);
padding: var(--space-md) var(--card-padding);
background: var(--bg-page);
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
.ant-card-head-title {
font-weight: var(--weight-semibold);
color: var(--text-primary);
}
.ant-card-body {
padding: var(--card-padding);
}
/* ===== 标签页统一样式 ===== */
.ant-tabs-nav {
margin-bottom: var(--space-md);
padding: 0 var(--space-xs);
}
.ant-tabs-tab {
padding: var(--tab-btn-padding-v) var(--tab-btn-padding-h);
font-weight: var(--weight-medium);
color: var(--text-secondary);
transition: all var(--transition-fast);
}
.ant-tabs-tab:hover {
color: var(--text-primary);
}
.ant-tabs-tab-active {
color: var(--text-primary) !important;
font-weight: var(--weight-semibold);
}
.ant-tabs-ink-bar {
background: var(--color-primary);
height: 2px;
border-radius: 1px;
}
/* ===== 按钮统一样式 ===== */
.ant-btn {
border-radius: var(--radius-md);
font-weight: var(--weight-medium);
transition: all var(--transition-fast);
}
.ant-btn-primary {
background: var(--color-primary);
border-color: var(--color-primary);
}
.ant-btn-primary:hover {
background: var(--color-primary-hover);
border-color: var(--color-primary-hover);
}
.ant-btn-default {
border-color: var(--border-default);
color: var(--text-primary);
}
.ant-btn-default:hover {
border-color: var(--color-primary);
color: var(--color-primary);
}
/* ===== 输入框统一样式 ===== */
.ant-input {
border-radius: var(--radius-md);
transition: all var(--transition-fast);
border-color: var(--border-default);
font-size: var(--font-md);
height: var(--input-height);
}
.ant-input:focus,
.ant-input:hover,
.ant-input-focused {
border-color: var(--color-primary);
box-shadow: 0 0 0 2px rgba(33, 37, 41, 0.08);
}
/* ===== 选择器统一样式 ===== */
.ant-select-selector {
border-radius: var(--radius-md) !important;
border-color: var(--border-default) !important;
transition: all var(--transition-fast) !important;
}
.ant-select:hover .ant-select-selector {
border-color: var(--border-medium) !important;
}
.ant-select-focused .ant-select-selector {
border-color: var(--color-primary) !important;
box-shadow: 0 0 0 2px rgba(33, 37, 41, 0.08) !important;
}
.ant-select-selection-item {
color: var(--text-primary) !important;
font-weight: var(--weight-medium);
}
.ant-select-selection-placeholder {
color: var(--text-disabled) !important;
}
.ant-select-dropdown {
border-radius: var(--radius-lg) !important;
box-shadow: var(--shadow-lg) !important;
border: 1px solid var(--border-light) !important;
}
.ant-select-item {
border-radius: var(--radius-md) !important;
margin: var(--space-xs) var(--space-sm) !important;
padding: var(--space-sm) var(--space-md) !important;
font-weight: var(--weight-medium);
color: var(--text-secondary);
}
.ant-select-item-option-selected {
background: var(--bg-active) !important;
color: var(--text-primary) !important;
font-weight: var(--weight-semibold);
}
.ant-select-item-option-active:not(.ant-select-item-option-disabled) {
background: var(--bg-hover) !important;
}
/* ===== 表格紧凑模式 ===== */
.ant-table {
border-radius: var(--radius-lg);
overflow: hidden;
box-shadow: var(--shadow-sm);
}
.ant-table-thead > tr > th {
background: var(--bg-container);
color: var(--text-primary);
font-weight: var(--weight-semibold);
font-size: var(--font-md);
padding: var(--space-sm) var(--space-md);
border-bottom: 1px solid var(--border-default);
}
.ant-table-tbody > tr > td {
font-size: var(--font-md);
padding: var(--space-sm) var(--space-md);
border-bottom: 1px solid var(--border-light);
}
.ant-table-tbody > tr:hover > td {
background: var(--bg-hover);
}
/* ===== 模态框统一样式 ===== */
.ant-modal-content {
border-radius: var(--radius-xl);
box-shadow: var(--shadow-xl);
}
.ant-modal-header {
border-radius: var(--radius-xl) var(--radius-xl) 0 0;
border-bottom: 1px solid var(--border-light);
padding: var(--space-lg) var(--space-2xl);
background: var(--bg-page);
}
.ant-modal-title {
font-weight: var(--weight-semibold);
font-size: var(--font-xl);
color: var(--text-primary);
}
.ant-modal-body {
padding: var(--space-2xl);
}
.ant-modal-footer {
border-top: 1px solid var(--border-light);
padding: var(--space-lg) var(--space-2xl);
border-radius: 0 0 var(--radius-xl) var(--radius-xl);
}
/* ===== 列表项统一样式 ===== */
.ant-list-item {
border-radius: var(--radius-md);
margin-bottom: var(--space-md);
padding: var(--space-md) var(--space-lg);
transition: all var(--transition-fast);
cursor: pointer;
border: 1px solid transparent;
}
.ant-list-item:hover {
background: var(--bg-hover);
border-color: var(--border-light);
}
/* ===== 标签统一样式 ===== */
.ant-tag {
border-radius: var(--radius-sm);
padding: 2px var(--space-sm);
font-weight: var(--weight-medium);
font-size: var(--font-sm);
}
/* ===== 表单统一样式 ===== */
.ant-form-item-label > label {
font-weight: var(--weight-medium);
color: var(--text-primary);
}
.ant-form-item {
margin-bottom: var(--form-gap);
}
/* ===== 进度条统一样式 ===== */
.ant-progress-bg {
border-radius: var(--radius-sm);
}
/* ===== 徽标统一样式 ===== */
.ant-badge-status-dot {
width: 8px;
height: 8px;
}
/* ===== 滚动条统一样式 ===== */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
border-radius: 3px;
}
::-webkit-scrollbar-thumb {
background: var(--border-dark);
border-radius: 3px;
transition: all var(--transition-fast);
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-tertiary);
}
/* ===== 标题样式 ===== */
h1, h2, h3, h4, h5, h6 {
color: var(--text-primary);
font-weight: var(--weight-semibold);
line-height: 1.4;
}
h3 {
font-size: var(--font-xl);
margin-bottom: var(--space-md);
}
h4 {
font-size: var(--font-lg);
margin-bottom: var(--space-sm);
}
h5 {
font-size: var(--font-md);
margin-bottom: var(--space-sm);
}
/* ===== 全局布局约束 ===== */
html {
overflow: hidden;
width: 100%;
height: 100%;
}
body {
overflow-x: hidden;
overflow-y: hidden;
width: 100vw;
max-width: 100vw;
height: 100vh;
}
#app {
width: 100vw;
max-width: 100vw;
height: 100vh;
overflow-x: hidden;
overflow-y: hidden;
}
.ant-layout {
max-width: 100vw;
width: 100vw;
height: 100vh;
overflow: hidden;
}
.ant-card,
.ant-table-wrapper,
.ant-list,
.ant-tabs,
.ant-form {
max-width: 100% !important;
}
/* ===== 性能友好降级 ===== */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
```
---
### Task 4: 简化 index.css 基础样式
**Files:**
- Modify: `src/index.css`
- [ ] **Step 1: 简化 index.css 为纯基础重置**
```css
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
```
---
### Task 5: 重构 Home.vue 导航系统
**Files:**
- Modify: `src/views/Home.vue`
- [ ] **Step 1: 将下拉选择器改为顶部标签栏导航**
关键修改点:
1. Header 高度从默认改为 48px (`var(--header-height)`)
2. 移除 `<a-select>` 下拉框
3. 新增顶部标签栏导航按钮组
4. 应用白色体系样式
5. 保持所有权限逻辑不变
```vue
<!-- Header 部分 - 从下拉框改为标签栏 -->
<a-layout-header style="height: var(--header-height); padding: 0 var(--space-xl); display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid var(--border-light); background: var(--bg-header);">
<div style="display: flex; align-items: center; gap: var(--space-xl);">
<h1 style="color: var(--text-primary); font-size: var(--font-lg); font-weight: var(--weight-semibold); margin: 0;">制度文件管理学习AI智能体</h1>
<!-- 顶部标签栏导航 -->
<div class="top-nav-bar">
<button
v-for="mod in visibleModules"
:key="mod.key"
:class="['nav-tab', { active: currentModule === mod.key }]"
@click="handleModuleChange(mod.key)"
>
<span class="nav-icon" v-html="mod.icon"></span>
<span class="nav-label">{{ mod.label }}</span>
</button>
</div>
</div>
<!-- 右侧用户区域保持不变 -->
...
</a-layout-header>
```
```js
// 新增计算属性 - 可见模块列表
const visibleModules = computed(() => {
const moduleConfig = [
{ key: 'read', label: '文件查看', icon: '📄' },
{ key: 'manage', label: '文件管理', icon: '📁' },
{ key: 'qa', label: '知识问答', icon: '💬' },
{ key: 'exam', label: '考察训练', icon: '🔍' },
{ key: 'mind', label: '纲要学习', icon: '📋' },
{ key: 'dashboard', label: '综合看板', icon: '📊' },
{ key: 'permission', label: '权限管理', icon: '👥' }
]
return moduleConfig.filter(mod => hasPermission(mod.key))
})
```
```css
/* 新增顶部导航样式 */
.top-nav-bar {
display: flex;
gap: var(--space-xs);
}
.nav-tab {
display: inline-flex;
align-items: center;
gap: var(--space-sm);
padding: var(--space-sm) var(--space-md);
border: none;
border-radius: var(--radius-md);
background: transparent;
color: var(--text-secondary);
font-size: var(--font-md);
font-weight: var(--weight-medium);
cursor: pointer;
transition: all var(--transition-fast);
white-space: nowrap;
}
.nav-tab:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.nav-tab.active {
background: var(--bg-active);
color: var(--text-primary);
font-weight: var(--weight-semibold);
}
.nav-icon {
font-size: 14px;
}
.nav-label {
line-height: 1;
}
```
---
### Task 6: 重构 ReadModule.vue
**Files:**
- Modify: `src/components/ReadModule.vue`
- [ ] **Step 1: 引入并使用 TabsBar 组件替代自定义 main-tabs-bar**
- [ ] **Step 2: 移除模块内重复的渐变样式类**
- [ ] **Step 3: 统一使用 CSS 变量定义的颜色值**
---
### Task 7: 重构 ExamModule.vue
**Files:**
- Modify: `src/components/ExamModule.vue`
- [ ] **Step 1: 引入并使用 TabsBar、PageHeader、StatsRow、ContentCard 组件**
- [ ] **Step 2: 替换 main-tabs-bar 为 TabsBar 组件**
- [ ] **Step 3: 替换 exam-header 为 PageHeader 组件**
- [ ] **Step 4: 替换 stats-row + stat-card 为 StatsRow 组件**
- [ ] **Step 5: 替换 exam-card 为 ContentCard 组件**
- [ ] **Step 6: 移除所有 btn-gradient-* 和 card-gradient-* 类的使用**
- [ ] **Step 7: 统一表单间距为 var(--form-gap)**
---
### Task 8: 重构 ManageModule.vue
**Files:**
- Modify: `src/components/ManageModule.vue`
- [ ] **Step 1: 引入并使用 TabsBar、PageHeader、ContentCard、DataTable 组件**
- [ ] **Step 2: 替换重复的布局结构为通用组件**
- [ ] **Step 3: 移除彩色渐变样式**
---
### Task 9: 重构 QAModule.vue
**Files:**
- Modify: `src/components/QAModule.vue`
- [ ] **Step 1: 引入并使用 PageHeader、ContentCard 组件**
- [ ] **Step 2: 简化页面头部和卡片容器**
---
### Task 10: 重构 MindModule.vue / DashboardModule.vue / PermissionModule.vue
**Files:**
- Modify: `src/components/MindModule.vue`
- Modify: `src/components/DashboardModule.vue`
- Modify: `src/components/PermissionModule.vue`
- [ ] **Step 1: 各模块分别引入对应通用组件**
- [ ] **Step 2: 替换重复布局结构**
- [ ] **Step 3: DashboardModule 特别处理 StatsRow 的使用**
---
### Task 11: 最终验证
- [ ] **Step 1: 启动开发服务器检查编译是否通过**
Run: `npm run dev`
Expected: 无编译错误
- [ ] **Step 2: 逐个模块检查视觉一致性**
检查项:
- 所有页面使用统一的白色背景
- Header 为纯白+底部边线
- 标签栏为灰色激活态
- 卡片为纯白+浅边框
- 按钮为深灰色主操作色
- 表格行高紧凑一致
- 无残留的彩色渐变元素
- [ ] **Step 3: 检查各模块功能完整性**
检查项:
- 登录流程正常
- 权限控制正常
- 模块切换正常
- 数据加载正常
- 交互操作正常
---
## 实施顺序总结
```
Task 1: design-tokens.css + main.js引入 ← 基础设施
Task 2: 6个通用组件 ← 组件库
Task 3: style.css 重写 ← 全局样式
Task 4: index.css 简化 ← 基础样式
Task 5: Home.vue 导航重构 ← 导航系统
Task 6: ReadModule.vue ← 模块重构
Task 7: ExamModule.vue ← 模块重构
Task 8: ManageModule.vue ← 模块重构
Task 9: QAModule.vue ← 模块重构
Task 10: MindModule/Dashboard/Permission ← 模块重构
Task 11: 最终验证 ← 质量保证
```