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

27 KiB
Raw Permalink Blame History

前端系统性重构 - 白色体系设计规范实施

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 文件

/* ========================================
   设计令牌系统 - 白色体系
   基于:前端优化方案与设计规范文档
   ======================================== */

: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' 之后添加:

import './design-tokens.css'

Task 2: 创建通用组件库

Task 2.1: 创建 PageHeader 组件

Files:

  • Create: src/components/common/PageHeader.vue

  • Step 1: 创建 PageHeader.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

<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

<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

<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

<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 替换为以下内容:

/* ========================================
   全局样式 - 白色体系
   基于设计令牌系统
   ======================================== */

* {
  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 为纯基础重置

* {
  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. 保持所有权限逻辑不变
<!-- 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>
// 新增计算属性 - 可见模块列表
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))
})
/* 新增顶部导航样式 */
.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: 最终验证                               ← 质量保证