从零构建技能展示页面:基于Element UI的实战开发指南

1次阅读
没有评论

共计 3052 个字符,预计需要花费 8 分钟才能阅读完成。

image.webp

开篇:为什么你的技能展示页面总是难以维护?

每次接到技能展示页面的需求时,很多开发者都会遇到这些典型问题:

从零构建技能展示页面:基于 Element UI 的实战开发指南

  • 组件臃肿:一个.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())

思考题:如何实现技能标签云端配置?

可以考虑的方案:

  1. 使用 JSON Schema 定义技能标签规范
  2. 通过 CDN 动态加载配置
  3. 结合 WebSocket 实现实时更新
  4. 利用 localStorage 做版本缓存

期待大家在评论区分享自己的实现方案!

正文完
 0
评论(没有评论)