共计 3052 个字符,预计需要花费 8 分钟才能阅读完成。
开篇:为什么你的技能展示页面总是难以维护?
每次接到技能展示页面的需求时,很多开发者都会遇到这些典型问题:

- 组件臃肿:一个.vue 文件动辄 500+ 行代码,技能卡片、标签、进度条全挤在一起
- 样式污染:全局 CSS 导致修改某个卡片样式时,其他页面同类组件也跟着遭殃
- 数据混乱:技能等级计算逻辑散落在 methods 各个角落,想调整评级标准时像在拆炸弹
- 响应式灾难:在移动端看到的技能图标堆叠成俄罗斯方块
技术选型:为什么是 Element UI?
对比当下主流 UI 框架的适用场景:
- Ant Design Vue:适合中后台系统,但预设样式较厚重
- Vuetify:Material Design 风格明显,定制成本较高
- Element UI:表单类组件丰富,主题变量覆盖全面,特别适合需要频繁展示评级、标签的场景
核心实现:构建技能卡片组件
1. 基础结构搭建
使用 el-card 作为容器,配合 el-tabs 实现分类切换:
<template>
<el-card class="skill-card">
<el-tabs v-model="activeTab">
<el-tab-pane
v-for="category in skillData"
:key="category.id"
:label="category.name"
>
<skill-item
v-for="skill in category.list"
:key="skill.id"
:skill="skill"
/>
</el-tab-pane>
</el-tabs>
</el-card>
</template>
<script setup>
import {ref} from 'vue'
defineProps({
skillData: {
type: Array,
required: true,
validator: (value) => {return value.every(item => 'id' in item && 'list' in item)
}
}
})
const activeTab = ref(0)
</script>
2. 技能项组件封装
独立 SkillItem 组件实现解耦:
<template>
<div class="skill-item">
<div class="header">
<span class="name">{{skill.name}}</span>
<el-rate
v-model="internalLevel"
:max="5"
disabled
/>
</div>
<el-progress
:percentage="calcExperience(skill.startDate)"
:status="progressStatus"
/>
</div>
</template>
<script setup>
import {computed} from 'vue'
const props = defineProps({
skill: {
type: Object,
required: true,
default: () => ({
id: '',
name: '',
level: 0,
startDate: ''
})
}
})
// 计算属性应该放在 setup 内部
const internalLevel = computed(() => Math.min(5, props.skill.level))
const calcExperience = (startDate) => {const years = new Date().getFullYear() - new Date(startDate).getFullYear()
return Math.min(100, years * 20)
}
const progressStatus = computed(() => {return internalLevel.value >= 4 ? 'success' : 'warning'})
</script>
<style scoped>
.skill-item {margin-bottom: 16px;}
.header {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
}
</style>
进阶优化技巧
1. 使用 mixins 统一进度逻辑
创建progressMixin.js:
export default {
methods: {calcProgress(startDate, maxYears = 5) {const years = new Date().getFullYear() - new Date(startDate).getFullYear()
return Math.min(100, (years / maxYears) * 100)
}
}
}
2. CSS 变量实现主题定制
在根元素定义变量:
:root {
--skill-primary: #409EFF;
--skill-warning: #E6A23C;
}
.el-progress-bar__inner {background-color: var(--skill-primary) !important;
}
.el-rate__icon {color: var(--skill-warning) !important;
}
避坑指南
1. 解决 el-collapse 动画卡顿
<el-collapse
:accordion="true"
@change="handleCollapseChange"
v-bind="collapseProps"
>
</el-collapse>
<script setup>
const collapseProps = {
css: false, // 禁用 CSS 过渡
duration: 200 // 自定义动画时长
}
const handleCollapseChange = (activeNames) => {
// 使用 requestAnimationFrame 优化性能
requestAnimationFrame(() => {// 处理逻辑})
}
</script>
2. v-for 与 v -if 的正确用法
错误示范:
<skill-item
v-for="skill in skills"
v-if="skill.visible"
:key="skill.id"
/>
正确做法:
<template v-for="skill in skills">
<skill-item
v-if="skill.visible"
:key="skill.id"
:skill="skill"
/>
</template>
数据对接实战
使用 axios 获取技能数据示例:
import {ref, onMounted} from 'vue'
import axios from 'axios'
const skillData = ref([])
const fetchSkills = async () => {
try {const { data} = await axios.get('/api/skills', {
params: {pageSize: 50}
})
skillData.value = data.groupBy(category => category.type)
} catch (error) {console.error('获取技能数据失败:', error)
ElMessage.error('数据加载失败')
}
}
onMounted(() => fetchSkills())
思考题:如何实现技能标签云端配置?
可以考虑的方案:
- 使用 JSON Schema 定义技能标签规范
- 通过 CDN 动态加载配置
- 结合 WebSocket 实现实时更新
- 利用 localStorage 做版本缓存
期待大家在评论区分享自己的实现方案!
正文完
发表至: 前端开发
近一天内
