27 KiB
前端系统性重构 - 白色体系设计规范实施
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: 将下拉选择器改为顶部标签栏导航
关键修改点:
- Header 高度从默认改为 48px (
var(--header-height)) - 移除
<a-select>下拉框 - 新增顶部标签栏导航按钮组
- 应用白色体系样式
- 保持所有权限逻辑不变
<!-- 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: 最终验证 ← 质量保证