【前端】Vue 3 课程选择组件开发实战:从设计到实现
文章目录
- 前言
- 一、组件设计思路
- 1. 需求分析
- 2. 技术选型
- 二、核心功能实现
- 1. 数据结构设计
- 2. 动态样式计算
- 3. 颜色处理工具函数
- 三、交互实现
- 1. 点击事件处理
- 2. 键盘可访问性
- 3. 状态管理
- 四、样式设计
- 1. 基础布局
- 2. 卡片效果
- 3. 交互状态
- 4. 响应式设计
- 五、完整页面代码
- 六、实现效果
- 七、优化与改进
- 1. 性能优化
- 2. 可访问性增强
- 3. 代码组织
- 总结
前言
今天我将分享一个基于 Vue 3 和 Element Plus 的课程选择组件的开发过程。这个组件不仅实现了基本的课程选择功能,还包含了精美的UI设计和良好的交互体验。下面我们将从组件设计、功能实现到样式优化进行全面解析。
一、组件设计思路
1. 需求分析
我们需要实现一个课程选择界面,要求:
- 以卡片形式展示多个课程
- 点击卡片时显示课程详情
- 有视觉反馈的交互效果
- 良好的可访问性支持
- 响应式布局适配不同设备
2. 技术选型
- Vue 3:使用
<script setup>
语法简化代码 - Element Plus:利用其 ElMessage 组件提供用户反馈
- CSS 变量:实现动态主题色
- 绝对定位:创建自由布局的卡片位置
二、核心功能实现
1. 数据结构设计
const courses = [{id: 'math',name: '高等数学',description: '涵盖微积分、线性代数等核心数学知识',color: '#f7b100',activeColor: '#ffcc00',position: { top: '0%', left: '0.2%', width: '18%', height: '48%' }},// 其他课程...
]
每个课程对象包含:
- 基础信息:id、name、description
- 样式配置:color、activeColor
- 布局参数:position 对象定义绝对定位属性
2. 动态样式计算
const getCourseStyle = (course) => {const isActive = activeId.value === course.idconst baseColor = isActive ? course.activeColor : course.colorconst darkenColor = adjustColor(baseColor, isActive ? -25 : -15)return {top: course.position.top,left: course.position.left,width: course.position.width,height: course.position.height,'--base-color': baseColor,'--darken-color': darkenColor,'--text-color': getContrastColor(baseColor)}
}
这里使用了 CSS 变量来实现动态主题色,通过计算属性返回样式对象。
3. 颜色处理工具函数
组件中实现了两个实用的颜色处理函数:
adjustColor 函数:调整颜色亮度
const adjustColor = (hexColor, percent) => {// 处理3位或6位hex颜色// 转换为RGB数值// 按百分比调整亮度// 返回新的hex颜色
}
getContrastColor 函数:自动计算对比色
const getContrastColor = (hexColor) => {// 计算颜色亮度// 根据亮度返回黑色或白色
}
三、交互实现
1. 点击事件处理
const handleCourseClick = (course) => {activeId.value = course.idElMessage.success({message: `已选中课程: ${course.name}`,duration: 1500})
}
使用 Element Plus 的 ElMessage 提供用户反馈,增强交互体验。
2. 键盘可访问性
<div @keydown.enter="handleCourseClick(course)"tabindex="0"role="button":aria-label="`选择课程${course.name}`"
>
添加了键盘事件支持和 ARIA 属性,使组件可以通过键盘操作。
3. 状态管理
使用 activeId
ref 来跟踪当前选中的课程,通过计算属性动态应用样式类:
:class="{ active: activeId === course.id }"
四、样式设计
1. 基础布局
.course-selection-container {position: relative;width: 100%;height: 600px;margin: 20px auto;
}.course-box {position: absolute;/* 其他样式... */
}
使用绝对定位创建自由布局,容器设置固定高度。
2. 卡片效果
.course-box {border-radius: 12px;cursor: pointer;transition: all 0.3s ease;background: linear-gradient(135deg, var(--base-color), var(--darken-color));box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
- 圆角边框
- 平滑过渡动画
- 渐变背景
- 阴影效果
3. 交互状态
.course-box:hover {transform: translateY(-5px) scale(1.02);
}.course-box.active {transform: scale(1.05);z-index: 10;box-shadow: 0 12px 24px rgba(0, 0, 0, 0.25);
}
- 悬停效果:轻微上浮和放大
- 激活状态:更大缩放和更高阴影
4. 响应式设计
@media (max-width: 768px) {.course-selection-container {height: 1000px;}.course-box {position: relative;width: 90% !important;height: 120px !important;margin: 10px auto;}
}
在小屏幕设备上,将绝对定位改为常规布局,垂直排列卡片。
五、完整页面代码
<template><div class="course-selection-container"><!-- 课程卡片组件 --><divv-for="course in courses":key="course.id"class="course-box":style="getCourseStyle(course)":class="{ active: activeId === course.id }"@click="handleCourseClick(course)"@keydown.enter="handleCourseClick(course)"tabindex="0"role="button":aria-label="`选择课程${course.name}`"><div class="course-name">{{ course.name }}</div><div class="course-desc" v-if="activeId === course.id">{{ course.description }}</div></div></div>
</template><script setup>
import { ref, computed } from 'vue'
import { ElMessage } from 'element-plus'// 当前选中的课程ID
const activeId = ref('math')// 课程数据配置
const courses = [{id: 'math',name: '高等数学',description: '涵盖微积分、线性代数等核心数学知识',color: '#f7b100',activeColor: '#ffcc00',position: { top: '0%', left: '0.2%', width: '18%', height: '48%' }},{id: 'physics',name: '大学物理',description: '力学、电磁学、热力学等物理基础',color: '#0096ff',activeColor: '#00b4ff',position: { top: '0%', left: '20.2%', width: '18%', height: '48%' }},{id: 'chemistry',name: '有机化学',description: '有机化合物结构与反应机理研究',color: '#32c832',activeColor: '#4ce64c',position: { top: '0%', left: '40.2%', width: '18%', height: '48%' }},{id: 'programming',name: '程序设计',description: '编程基础与算法思维训练',color: '#ff6400',activeColor: '#ff8200',position: { top: '0%', left: '60.2%', width: '18%', height: '48%' }},{id: 'english',name: '学术英语',description: '学术写作与专业英语能力提升',color: '#6464ff',activeColor: '#8282ff',position: { top: '50%', left: '0.2%', width: '18%', height: '48%' }},{id: 'history',name: '世界历史',description: '全球文明发展与历史事件分析',color: '#b464b4',activeColor: '#d282d2',position: { top: '50%', left: '20.2%', width: '18%', height: '48%' }},{id: 'art',name: '艺术设计',description: '视觉艺术原理与创意设计实践',color: '#ff3296',activeColor: '#ff50b4',position: { top: '50%', left: '40.2%', width: '18%', height: '48%' }}
]// 计算课程卡片样式
const getCourseStyle = (course) => {const isActive = activeId.value === course.idconst baseColor = isActive ? course.activeColor : course.colorconst darkenColor = adjustColor(baseColor, isActive ? -25 : -15)return {top: course.position.top,left: course.position.left,width: course.position.width,height: course.position.height,'--base-color': baseColor,'--darken-color': darkenColor,'--text-color': getContrastColor(baseColor)}
}// 处理课程点击
const handleCourseClick = (course) => {activeId.value = course.idElMessage.success({message: `已选中课程: ${course.name}`,duration: 1500})
}// 颜色调整工具函数(优化版)
const adjustColor = (hexColor, percent) => {// 确保hexColor是6位十六进制格式let hex = hexColor.replace('#', '')if (hex.length === 3) {hex = hex.split('').map(x => x + x).join('')}// 转换为RGBlet r = parseInt(hex.substring(0, 2), 16)let g = parseInt(hex.substring(2, 4), 16)let b = parseInt(hex.substring(4, 6), 16)// 调整亮度r = Math.min(255, Math.max(0, r + Math.round(r * percent / 100)))g = Math.min(255, Math.max(0, g + Math.round(g * percent / 100)))b = Math.min(255, Math.max(0, b + Math.round(b * percent / 100)))// 返回hex格式return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`
}// 获取对比色(确保文字可读性)
const getContrastColor = (hexColor) => {const hex = hexColor.replace('#', '')const r = parseInt(hex.substring(0, 2), 16)const g = parseInt(hex.substring(2, 4), 16)const b = parseInt(hex.substring(4, 6), 16)const brightness = (r * 299 + g * 587 + b * 114) / 1000return brightness > 128 ? '#333' : '#fff'
}
</script><style scoped>
.course-selection-container {position: relative;width: 100%;height: 600px;margin: 20px auto;
}.course-box {position: absolute;border-radius: 12px;cursor: pointer;transition: all 0.3s ease;display: flex;flex-direction: column;align-items: center;justify-content: center;box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);background: linear-gradient(135deg, var(--base-color), var(--darken-color));color: var(--text-color);overflow: hidden;outline: none;
}.course-box:hover {transform: translateY(-5px) scale(1.02);box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
}.course-box.active {transform: scale(1.05);z-index: 10;box-shadow: 0 12px 24px rgba(0, 0, 0, 0.25);
}.course-box:focus-visible {outline: 2px solid var(--darken-color);outline-offset: 2px;
}.course-name {font-size: 1.25rem;font-weight: bold;text-align: center;padding: 12px;transition: all 0.3s ease;
}.course-desc {font-size: 0.875rem;padding: 0 12px 12px;text-align: center;max-width: 90%;opacity: 0;max-height: 0;transition: all 0.3s ease;
}.course-box.active .course-desc {opacity: 1;max-height: 100px;
}@media (max-width: 768px) {.course-selection-container {height: 1000px;}.course-box {position: relative;top: auto !important;left: auto !important;width: 90% !important;height: 120px !important;margin: 10px auto;}
}
</style>
六、实现效果
七、优化与改进
1. 性能优化
- 使用 CSS 变量减少重复计算
- 避免在模板中进行复杂计算
- 合理使用 transition 实现平滑动画
2. 可访问性增强
- 添加 tabindex 使元素可聚焦
- 使用 ARIA 属性描述元素
- 键盘事件支持
- 高对比度文字颜色
3. 代码组织
- 将样式计算逻辑提取到单独函数
- 使用计算属性缓存结果
- 清晰的代码注释
总结
这个课程选择组件展示了 Vue 3 的多种特性应用:
- 使用
<script setup>
简化组合式 API 代码 - 动态样式绑定和 CSS 变量实现主题化
- 完善的交互状态管理
- 响应式布局设计
- 可访问性最佳实践
组件可以轻松扩展:
- 添加更多课程只需在数据中增加配置
- 可以通过 props 接收外部课程数据
- 可以添加 emit 事件实现父子组件通信
希望这个实现案例对你的 Vue 开发有所帮助,你可以根据实际需求调整样式和功能。