Element Plus 组件库实战技巧与踩坑记录

0 阅读6分钟

🎨 Element Plus 组件库实战技巧与踩坑记录

分享我在Vue 3项目中使用Element Plus的经验技巧和踩坑记录

前言

Element Plus是Vue 3生态中最流行的UI组件库之一,提供了丰富的组件和良好的设计。在开发博客项目的过程中,我积累了很多使用Element Plus的经验和技巧,也踩过一些坑。本文将分享这些实战经验。

快速上手

1. 安装与配置

# 安装Element Plus
npm install element-plus

# 安装图标库
npm install @element-plus/icons-vue
// main.ts
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import App from './App.vue'

const app = createApp(App)

// 注册所有组件
app.use(ElementPlus)

// 注册所有图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}

app.mount('#app')

2. 按需引入(推荐)

为了减小包体积,建议按需引入组件:

# 安装按需引入插件
npm install -D unplugin-vue-components unplugin-auto-import
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
})

这样配置后,使用组件时会自动按需引入,无需手动import。

常用组件技巧

1. 表单组件

el-form深度验证
<template>
  <el-form
    ref="formRef"
    :model="formData"
    :rules="rules"
    label-width="120px"
  >
    <el-form-item label="标题" prop="title">
      <el-input v-model="formData.title" />
    </el-form-item>

    <el-form-item label="邮箱" prop="email">
      <el-input v-model="formData.email" />
    </el-form-item>

    <el-form-item label="密码" prop="password">
      <el-input
        v-model="formData.password"
        type="password"
        show-password
      />
    </el-form-item>

    <el-form-item>
      <el-button type="primary" @click="handleSubmit">
        提交
      </el-button>
      <el-button @click="handleReset">
        重置
      </el-button>
    </el-form-item>
  </el-form>
</template>

<script setup lang="ts">
import type { FormInstance, FormRules } from 'element-plus'

const formRef = ref<FormInstance>()

const formData = reactive({
  title: '',
  email: '',
  password: ''
})

const rules = reactive<FormRules>({
  title: [
    { required: true, message: '请输入标题', trigger: 'blur' },
    { min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
  ],
  email: [
    { required: true, message: '请输入邮箱地址', trigger: 'blur' },
    { type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }
  ],
  password: [
    { required: true, message: '请输入密码', trigger: 'blur' },
    { min: 6, message: '密码长度不能少于 6 位', trigger: 'blur' }
  ]
})

const handleSubmit = async () => {
  if (!formRef.value) return

  await formRef.value.validate((valid, fields) => {
    if (valid) {
      // 验证通过,提交表单
      console.log('提交:', formData)
    } else {
      console.log('验证失败:', fields)
    }
  })
}

const handleReset = () => {
  formRef.value?.resetFields()
}
</script>
动态表单
<template>
  <el-form :model="formData">
    <el-form-item
      v-for="(item, index) in formData.items"
      :key="index"
      :label="'项目 ' + (index + 1)"
    >
      <el-input v-model="item.value" />
      <el-button
        @click="removeItem(index)"
        icon="Delete"
        type="danger"
      >
        删除
      </el-button>
    </el-form-item>

    <el-button @click="addItem" icon="Plus">
      添加项目
    </el-button>
  </el-form>
</template>

<script setup lang="ts">
const formData = reactive({
  items: [{ value: '' }]
})

const addItem = () => {
  formData.items.push({ value: '' })
}

const removeItem = (index: number) => {
  formData.items.splice(index, 1)
}
</script>

2. 表格组件

表格排序和筛选
<template>
  <el-table
    :data="filteredData"
    :default-sort="{ prop: 'date', order: 'descending' }"
    @sort-change="handleSortChange"
  >
    <el-table-column prop="title" label="标题" sortable />
    <el-table-column
      prop="category"
      label="分类"
      :filters="categoryFilters"
      :filter-method="filterCategory"
    />
    <el-table-column prop="views" label="浏览量" sortable />
    <el-table-column prop="date" label="日期" sortable />
  </el-table>
</template>

<script setup lang="ts">
const articles = ref<Article[]>([])

const filteredData = computed(() => {
  return articles.value
})

const categoryFilters = [
  { text: 'Vue', value: 'Vue' },
  { text: 'React', value: 'React' },
  { text: 'TypeScript', value: 'TypeScript' }
]

const filterCategory = (value: string, row: Article) => {
  return row.category === value
}

const handleSortChange = (sort: any) => {
  console.log('排序改变:', sort)
}
</script>
表格分页
<template>
  <el-table :data="paginatedData">
    <!-- 列定义 -->
  </el-table>

  <el-pagination
    v-model:current-page="currentPage"
    v-model:page-size="pageSize"
    :total="total"
    :page-sizes="[10, 20, 50, 100]"
    layout="total, sizes, prev, pager, next, jumper"
    @size-change="handleSizeChange"
    @current-change="handleCurrentChange"
  />
</template>

<script setup lang="ts">
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0)

const paginatedData = computed(() => {
  const start = (currentPage.value - 1) * pageSize.value
  const end = start + pageSize.value
  return articles.value.slice(start, end)
})

const handleSizeChange = (size: number) => {
  pageSize.value = size
}

const handleCurrentChange = (page: number) => {
  currentPage.value = page
}
</script>

3. 弹窗组件

对话框嵌套
<template>
  <el-button @click="showDialog = true">打开对话框</el-button>

  <el-dialog v-model="showDialog" title="父对话框">
    <p>这是父对话框的内容</p>

    <el-button @click="showChildDialog = true">
      打开子对话框
    </el-button>

    <el-dialog
      v-model="showChildDialog"
      title="子对话框"
      append-to-body
    >
      <p>这是子对话框的内容</p>
    </el-dialog>
  </el-dialog>
</template>

<script setup lang="ts">
const showDialog = ref(false)
const showChildDialog = ref(false)
</script>

注意:嵌套对话框时,子对话框需要添加append-to-body属性。

4. 树形组件

异步加载树
<template>
  <el-tree
    :props="defaultProps"
    :load="loadNode"
    lazy
    show-checkbox
  />
</template>

<script setup lang="ts">
const defaultProps = {
  label: 'name',
  children: 'children',
  isLeaf: 'leaf'
}

const loadNode = async (node: Node, resolve: (data: TreeData[]) => void) => {
  if (node.level === 0) {
    // 加载根节点
    const data = await loadRootNodes()
    resolve(data)
  } else {
    // 加载子节点
    const data = await loadChildNodes(node.data.id)
    resolve(data)
  }
}

const loadRootNodes = async () => {
  // 异步加载数据
  return [
    { name: '节点1', id: 1 },
    { name: '节点2', id: 2 }
  ]
}
</script>

主题定制

1. 使用CSS变量

// styles/theme.scss
:root {
  --el-color-primary: #409eff;
  --el-color-success: #67c23a;
  --el-color-warning: #e6a23c;
  --el-color-danger: #f56c6c;
  --el-color-info: #909399;
}

// 使用自定义主题
$--color-primary: var(--el-color-primary);

2. SCSS变量覆盖

// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ElementPlus from 'unplugin-element-plus/vite'

export default defineConfig({
  plugins: [
    vue(),
    ElementPlus({
      // 使用scss样式
      useSource: true
    })
  ]
})
// styles/element-variables.scss
/* 改变主题色变量 */
$--color-primary: #1890ff;
$--color-success: #52c41a;
$--color-warning: #faad14;
$--color-danger: #f5222d;
$--color-info: #909399;

/* 改变icon字体路径变量,必需 */
$--font-path: '~element-plus/lib/theme-chalk/fonts';

@import "~element-plus/packages/theme-chalk/src/index";

3. 暗黑模式

<template>
  <el-switch
    v-model="isDark"
    @change="toggleDark"
    inline-prompt
    active-text="暗"
    inactive-text="亮"
  />
</template>

<script setup lang="ts">
const isDark = ref(false)

const toggleDark = (value: boolean) => {
  if (value) {
    document.documentElement.classList.add('dark')
  } else {
    document.documentElement.classList.remove('dark')
  }
}
</script>

<style>
/* 暗黑模式样式 */
html.dark {
  --el-bg-color: #141414;
  --el-text-color-primary: #e5eaf3;
  --el-border-color: #4c4d4f;
  --el-border-color-light: #414243;
}
</style>

性能优化

1. 图标按需加载

// utils/icons.ts
import { registerIcons } from 'element-plus/es/components/icon'

// 只注册需要的图标
export function lazyRegisterIcons() {
  const icons = [
    'Edit',
    'Delete',
    'View',
    'Download',
    'Share',
    'Star',
    'Plus',
    'Search',
    'Home'
  ]

  // 使用requestIdleCallback在空闲时注册
  const idleCallback = window.requestIdleCallback || window.setTimeout

  idleCallback(() => {
    registerIcons(icons)
  })
}

// main.ts
import { lazyRegisterIcons } from './utils/icons'
lazyRegisterIcons()

2. 虚拟滚动

<template>
  <el-virtual-list
    :data="items"
    :height="400"
    :item-size="50"
  >
    <template #default="{ item, index }">
      <div class="item">
        {{ index }} - {{ item.name }}
      </div>
    </template>
  </el-virtual-list>
</template>

<script setup lang="ts">
import { ElVirtualList } from 'element-plus'

// 生成大量数据
const items = Array.from({ length: 10000 }, (_, i) => ({
  id: i,
  name: `Item ${i}`
}))
</script>

踩坑记录

1. Dialog关闭不触发事件

问题:点击遮罩层关闭Dialog时,没有触发关闭事件。

解决:使用before-close属性:

<el-dialog
  v-model="visible"
  :before-close="handleClose"
>
  <template #header>
    <span>标题</span>
  </template>
</el-dialog>

<script setup lang="ts">
const handleClose = (done: () => void) => {
  // 执行关闭前的逻辑
  done()
}
</script>

2. Table固定列错位

问题:表格固定列在滚动时出现错位。

解决:监听窗口大小变化,调用doLayout方法:

<template>
  <el-table
    ref="tableRef"
    :data="tableData"
  >
    <el-table-column prop="date" label="日期" fixed />
    <el-table-column prop="name" label="姓名" />
  </el-table>
</template>

<script setup lang="ts">
const tableRef = ref()

onMounted(() => {
  window.addEventListener('resize', () => {
    tableRef.value?.doLayout()
  })
})
</script>

3. Select下拉框显示位置错误

问题:Select组件的下拉框在页面滚动后显示位置错误。

解决:使用popper-options配置:

<el-select
  v-model="value"
  :popper-options="{
    modifiers: [
      {
        name: 'flip',
        options: {
          fallbackPlacements: ['bottom-start', 'top-start']
        }
      }
    ]
  }"
>
  <el-option
    v-for="item in options"
    :key="item.value"
    :label="item.label"
    :value="item.value"
  />
</el-select>

4. DatePicker时间格式问题

问题:DatePicker返回的日期格式不符合预期。

解决:使用value-format属性:

<el-date-picker
  v-model="date"
  type="datetime"
  value-format="YYYY-MM-DD HH:mm:ss"
  placeholder="选择日期时间"
/>

5. Upload组件上传失败

问题:Upload组件在某些情况下上传失败。

解决:正确处理on-successon-error回调:

<el-upload
  action="/api/upload"
  :on-success="handleSuccess"
  :on-error="handleError"
  :before-upload="beforeUpload"
>
  <el-button type="primary">上传文件</el-button>
</el-upload>

<script setup lang="ts">
const handleSuccess = (response: any, file: any) => {
  if (response.code === 200) {
    ElMessage.success('上传成功')
  } else {
    ElMessage.error(response.message)
  }
}

const handleError = (error: any) => {
  ElMessage.error('上传失败:' + error.message)
}

const beforeUpload = (file: File) => {
  const isJPG = file.type === 'image/jpeg' || file.type === 'image/png'
  const isLt2M = file.size / 1024 / 1024 < 2

  if (!isJPG) {
    ElMessage.error('只能上传JPG/PNG图片!')
  }
  if (!isLt2M) {
    ElMessage.error('图片大小不能超过2MB!')
  }
  return isJPG && isLt2M
}
</script>

最佳实践

1. 统一配置

// config/element-plus.ts
import { ElConfigProvider } from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'

export default {
  locale: zhCn,
  size: 'default',
  zIndex: 3000
}
<!-- App.vue -->
<template>
  <el-config-provider :locale="locale">
    <router-view />
  </el-config-provider>
</template>

<script setup lang="ts">
import zhCn from 'element-plus/es/locale/lang/zh-cn'
const locale = zhCn
</script>

2. 封装常用组件

<!-- components/SearchInput.vue -->
<template>
  <el-input
    v-model="searchText"
    :placeholder="placeholder"
    clearable
    @clear="handleClear"
    @input="handleInput"
  >
    <template #prefix>
      <el-icon><Search /></el-icon>
    </template>
    <template #suffix>
      <el-button
        v-if="searchText"
        link
        icon="Close"
        @click="handleClear"
      />
    </template>
  </el-input>
</template>

<script setup lang="ts">
import { ref, watch } from 'vue'

interface Props {
  modelValue: string
  placeholder?: string
}

const props = withDefaults(defineProps<Props>(), {
  placeholder: '请输入搜索内容'
})

const emit = defineEmits<{
  (e: 'update:modelValue', value: string): void
  (e: 'search', value: string): void
}>()

const searchText = ref(props.modelValue)

watch(() => props.modelValue, (val) => {
  searchText.value = val
})

watch(searchText, (val) => {
  emit('update:modelValue', val)
})

const handleClear = () => {
  searchText.value = ''
  emit('search', '')
}

const handleInput = debounce((value: string) => {
  emit('search', value)
}, 300)
</script>

3. 全局样式覆盖

// styles/element-overrides.scss

// 全局修改el-button样式
.el-button {
  border-radius: 4px;
  font-weight: 500;

  &--primary {
    background-color: #1890ff;
    border-color: #1890ff;

    &:hover {
      background-color: #40a9ff;
      border-color: #40a9ff;
    }
  }
}

// 修改el-dialog样式
.el-dialog {
  border-radius: 8px;
  overflow: hidden;

  .el-dialog__header {
    padding: 20px 20px 10px;
    border-bottom: 1px solid #f0f0f0;
  }

  .el-dialog__body {
    padding: 20px;
  }
}

总结

Element Plus是一个功能强大、设计优秀的UI组件库,掌握以下要点可以更好地使用它:

  1. 按需引入 - 减小包体积
  2. 主题定制 - 符合项目风格
  3. 性能优化 - 图标懒加载、虚拟滚动
  4. 踩坑经验 - 了解常见问题和解决方案
  5. 最佳实践 - 封装常用组件、统一配置

希望这些经验能帮助你在Vue 3项目中更好地使用Element Plus!


标签:#ElementPlus #Vue3 #UI组件库 #前端 #实战技巧

点赞❤️ + 收藏⭐️ + 评论💬,你的支持是我创作的动力!