跳转到内容

Element Plus

Element Plus 是基于 Vue 3 的桌面端组件库,覆盖表单、表格、弹窗、导航、反馈等常用组件。本文整理后台管理系统中使用频率最高的组件,附完整代码示例,可直接复制使用。


一、快速开始

1.1 安装

npm install element-plus

1.2 引入方式

方式一:全局引入(简单项目)

// main.js
import { createApp } from 'vue';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import App from './App.vue';

const app = createApp(App);
app.use(ElementPlus);
app.mount('#app');

方式二:按需引入(推荐,减小打包体积)

// 使用 unplugin-element-plus 自动导入(推荐)
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import ElementPlus from 'unplugin-element-plus/vite';

export default defineConfig({
  plugins: [vue(), ElementPlus()],
});

// 或在需要的组件中手动引入
import { ElButton, ElInput } from 'element-plus';
import 'element-plus/theme-chalk/el-button.css';
import 'element-plus/theme-chalk/el-input.css';

方式三:使用 unplugin-vue-components(最推荐)

// vite.config.js
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: [
    AutoImport({ resolvers: [ElementPlusResolver()] }),
    Components({ resolvers: [ElementPlusResolver()] }),
  ],
});
// 组件和 API 自动导入,无需手动 import!直接使用 <el-button> 和 ElMessage 等

1.3 图标使用

npm install @element-plus/icons-vue
<template>
  <el-icon :size="20" color="#409EFF"><Edit /></el-icon>
  <el-button type="primary"><el-icon style="margin-right:6px"><Search /></el-icon>搜索</el-button>
</template>

<script setup>
import { Edit, Search, Delete, Plus, Download, Upload } from '@element-plus/icons-vue';
</script>

二、表单组件(最核心)

2.1 基础表单 + 校验

<template>
  <el-form ref="formRef" :model="form" :rules="rules" label-width="100px" size="default">
    <!-- 输入框 -->
    <el-form-item label="用户名" prop="username">
      <el-input v-model="form.username" placeholder="请输入用户名" clearable />
    </el-form-item>

    <!-- 密码 -->
    <el-form-item label="密码" prop="password">
      <el-input v-model="form.password" type="password" show-password placeholder="请输入密码" />
    </el-form-item>

    <!-- 下拉选择 -->
    <el-form-item label="角色" prop="role">
      <el-select v-model="form.role" placeholder="请选择角色" filterable clearable>
        <el-option label="管理员" value="admin" />
        <el-option label="普通用户" value="user" />
        <el-option label="访客" value="guest" />
      </el-select>
    </el-form-item>

    <!-- 日期选择 -->
    <el-form-item label="创建日期" prop="date">
      <el-date-picker v-model="form.date" type="date" placeholder="选择日期"
        value-format="YYYY-MM-DD" />
    </el-form-item>

    <!-- 单选 -->
    <el-form-item label="性别" prop="gender">
      <el-radio-group v-model="form.gender">
        <el-radio value="male">男</el-radio>
        <el-radio value="female">女</el-radio>
      </el-radio-group>
    </el-form-item>

    <!-- 多选 -->
    <el-form-item label="爱好" prop="hobbies">
      <el-checkbox-group v-model="form.hobbies">
        <el-checkbox label="reading">阅读</el-checkbox>
        <el-checkbox label="coding">编程</el-checkbox>
        <el-checkbox label="sports">运动</el-checkbox>
      </el-checkbox-group>
    </el-form-item>

    <!-- 开关 -->
    <el-form-item label="状态" prop="active">
      <el-switch v-model="form.active" active-text="启用" inactive-text="禁用" />
    </el-form-item>

    <!-- 数字输入 -->
    <el-form-item label="排序" prop="sort">
      <el-input-number v-model="form.sort" :min="0" :max="999" :step="10" />
    </el-form-item>

    <!-- 文本域 -->
    <el-form-item label="备注" prop="remark">
      <el-input v-model="form.remark" type="textarea" :rows="3" maxlength="200" show-word-limit />
    </el-form-item>

    <!-- 操作按钮 -->
    <el-form-item>
      <el-button type="primary" @click="handleSubmit(formRef)">提交</el-button>
      <el-button @click="handleReset(formRef)">重置</el-button>
    </el-form-item>
  </el-form>
</template>

<script setup>
import { ref, reactive } from 'vue';

const formRef = ref(null);

const form = reactive({
  username: '',
  password: '',
  role: '',
  date: '',
  gender: 'male',
  hobbies: [],
  active: true,
  sort: 0,
  remark: '',
});

// 校验规则
const rules = {
  username: [
    { required: true, message: '请输入用户名', trigger: 'blur' },
    { min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' },
  ],
  password: [
    { required: true, message: '请输入密码', trigger: 'blur' },
    { min: 6, message: '密码至少 6 位', trigger: 'blur' },
  ],
  role: [{ required: true, message: '请选择角色', trigger: 'change' }],
  date: [{ required: true, message: '请选择日期', trigger: 'change' }],
  hobbies: [{ type: 'array', required: true, message: '请选择爱好', trigger: 'change' }],
};

// 自定义校验器示例
const validatePassword = (rule, value, callback) => {
  if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value)) {
    callback(new Error('密码需包含大小写字母和数字'));
  }
  callback();
};

const handleSubmit = async (formEl) => {
  if (!formEl) return;
  await formEl.validate((valid, fields) => {
    if (valid) {
      console.log('提交数据:', form);
      // 调用 API
    } else {
      console.log('校验失败:', fields);
    }
  });
};

const handleReset = (formEl) => {
  if (!formEl) return;
  formEl.resetFields();
};
</script>

2.2 上传组件

<template>
  <!-- 图片上传 -->
  <el-upload
    action="/api/upload"
    list-type="picture-card"
    :file-list="fileList"
    :before-upload="beforeUpload"
    :on-success="onSuccess"
    :on-error="onError"
    :limit="3"
    :on-exceed="() => ElMessage.warning('最多上传 3 张')">
    <el-icon><Plus /></el-icon>
  </el-upload>

  <!-- 拖拽上传 -->
  <el-upload drag action="/api/upload" multiple :on-success="onSuccess">
    <el-icon class="el-icon--upload"><UploadFilled /></el-icon>
    <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
  </el-upload>
</template>

<script setup>
import { ElMessage } from 'element-plus';

const beforeUpload = (file) => {
  const isImage = file.type.startsWith('image/');
  const isLt2M = file.size / 1024 / 1024 < 2;
  if (!isImage) { ElMessage.error('只能上传图片文件!'); return false; }
  if (!isLt2M) { ElMessage.error('图片大小不能超过 2MB!'); return false; }
  return true;
};

const onSuccess = (response) => {
  ElMessage.success('上传成功');
};
const onError = () => {
  ElMessage.error('上传失败');
};
</script>

2.3 表单组件速查

组件

标签

关键属性

输入框

&lt;el-input&gt;

clearable, show-password, type="textarea", maxlength, show-word-limit

下拉选择

&lt;el-select&gt; + &lt;el-option&gt;

multiple(多选), filterable(可搜索), remote(远程搜索), clearable

单选

&lt;el-radio-group&gt; + &lt;el-radio&gt;

v-model 绑定, disabled

多选

&lt;el-checkbox-group&gt; + &lt;el-checkbox&gt;

v-model 绑定数组, :indeterminate(半选)

日期选择

&lt;el-date-picker&gt;

type="date|datetime|daterange|datetimerange", value-format, disabledDate

时间选择

&lt;el-time-picker&gt;

is-range(时间范围), format="HH:mm"

数字输入

&lt;el-input-number&gt;

:min, :max, :step, controls-position="right"

开关

&lt;el-switch&gt;

active-text, inactive-text, active-value, inactive-value

滑块

&lt;el-slider&gt;

:min, :max, :step, range(范围选择), show-stops

评分

&lt;el-rate&gt;

:max, show-score, allow-half(半星)

颜色选择器

&lt;el-color-picker&gt;

show-alpha, predefine(预设色)

级联选择器

&lt;el-cascader&gt;

:options, :props, filterable, clearable

穿梭框

&lt;el-transfer&gt;

:data, v-model, :titles, filterable

自动完成

&lt;el-autocomplete&gt;

:fetch-suggestions, value-key

上传

&lt;el-upload&gt;

action, :before-upload, :on-success, :limit, drag


三、表格组件

3.1 基础表格 + 分页 + 操作列

<template>
  <!-- 搜索栏 -->
  <el-form :model="query" inline>
    <el-form-item label="关键词">
      <el-input v-model="query.keyword" placeholder="搜索" clearable @clear="fetchData" />
    </el-form-item>
    <el-form-item label="状态">
      <el-select v-model="query.status" placeholder="状态" clearable @change="fetchData">
        <el-option label="启用" :value="1" />
        <el-option label="禁用" :value="0" />
      </el-select>
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="fetchData">搜索</el-button>
      <el-button @click="handleReset">重置</el-button>
    </el-form-item>
  </el-form>

  <!-- 表格 -->
  <el-table :data="tableData" border stripe v-loading="loading" @selection-change="handleSelectionChange">
    <el-table-column type="selection" width="55" />
    <el-table-column type="index" label="#" width="60" />
    <el-table-column prop="name" label="名称" min-width="120" show-overflow-tooltip />
    <el-table-column prop="status" label="状态" width="100">
      <template #default="{ row }">
        <el-tag :type="row.status === 1 ? 'success' : 'danger'">
          {{ row.status === 1 ? '启用' : '禁用' }}
        </el-tag>
      </template>
    </el-table-column>
    <el-table-column prop="sort" label="排序" width="80" align="center" />
    <el-table-column prop="createTime" label="创建时间" width="180" />
    <el-table-column label="操作" width="200" fixed="right">
      <template #default="{ row }">
        <el-button size="small" @click="handleEdit(row)">编辑</el-button>
        <el-button size="small" type="danger" @click="handleDelete(row)">删除</el-button>
      </template>
    </el-table-column>
  </el-table>

  <!-- 分页 -->
  <el-pagination
    v-model:current-page="pagination.page"
    v-model:page-size="pagination.size"
    :total="pagination.total"
    :page-sizes="[10, 20, 50, 100]"
    layout="total, sizes, prev, pager, next, jumper"
    background
    @size-change="fetchData"
    @current-change="fetchData" />
</template>

<script setup>
import { ref, reactive, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';

const loading = ref(false);
const tableData = ref([]);
const selectedRows = ref([]);

const query = reactive({ keyword: '', status: '' });
const pagination = reactive({ page: 1, size: 10, total: 0 });

const fetchData = async () => {
  loading.value = true;
  try {
    const { data } = await api.getList({
      ...query,
      page: pagination.page,
      size: pagination.size,
    });
    tableData.value = data.list;
    pagination.total = data.total;
  } finally {
    loading.value = false;
  }
};

const handleSelectionChange = (rows) => {
  selectedRows.value = rows;
};

const handleDelete = (row) => {
  ElMessageBox.confirm('确认删除该项?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(async () => {
    await api.delete(row.id);
    ElMessage.success('删除成功');
    fetchData();
  }).catch(() => {});
};

const handleReset = () => {
  query.keyword = '';
  query.status = '';
  fetchData();
};

onMounted(() => fetchData());
</script>

3.2 表格进阶特性

自定义列内容(插槽 + 操作)

<el-table :data="data">
  <!-- 图片列 -->
  <el-table-column label="头像" width="80">
    <template #default="{ row }">
      <el-avatar :size="40" :src="row.avatar" />
    </template>
  </el-table-column>

  <!-- 格式化列 -->
  <el-table-column label="金额" width="120" align="right">
    <template #default="{ row }">
      ¥{{ row.amount.toFixed(2) }}
    </template>
  </el-table-column>

  <!-- 条件渲染列 -->
  <el-table-column label="状态" width="100">
    <template #default="{ row }">
      <el-tag :type="statusMap[row.status]?.type" effect="plain">
        {{ statusMap[row.status]?.text }}
      </el-tag>
    </template>
  </el-table-column>
</el-table>

<script setup>
const statusMap = {
  0: { text: '待审核', type: 'warning' },
  1: { text: '已通过', type: 'success' },
  2: { text: '已拒绝', type: 'danger' },
};
</script>

表格常用属性速查

属性

说明

示例值

data

表格数据

[{...}]

border

竖向边框

true

stripe

斑马纹

true

v-loading

加载中

boolean

size

尺寸

large / default / small

fit

列宽度自撑开

true(默认)

show-header

显示表头

true(默认)

highlight-current-row

高亮当前行

true

row-key

行数据的 Key

"id"(展开行/树形数据必须用)

tree-props

树形数据配置

{ children, hasChildren }

empty-text

空数据文案

"暂无数据"

max-height

最大高度(超出固定表头滚动)

400

show-overflow-tooltip

超出省略 + tooltip

true(列属性)

fixed

固定列

"left" / "right"(列属性)

el-pagination 分页属性

属性

说明

示例

v-model:current-page

当前页

1

v-model:page-size

每页条数

10

total

总条数

100

page-sizes

每页条数选项

[10, 20, 50]

layout

布局

"total, sizes, prev, pager, next, jumper"

background

背景色

true

small

小型分页

true


四、弹窗与对话框

4.1 Dialog(对话框 / 模态框)

<template>
  <!-- 触发按钮 -->
  <el-button type="primary" @click="dialogVisible = true">打开对话框</el-button>

  <!-- 对话框 -->
  <el-dialog
    v-model="dialogVisible"
    :title="isEdit ? '编辑' : '新增'"
    width="600px"
    :close-on-click-modal="false"
    @closed="handleClosed">
    <!-- 对话框内容:表单 -->
    <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
      <el-form-item label="名称" prop="name">
        <el-input v-model="form.name" />
      </el-form-item>
      <!-- 更多表单项... -->
    </el-form>

    <!-- 底部按钮 -->
    <template #footer>
      <el-button @click="dialogVisible = false">取消</el-button>
      <el-button type="primary" :loading="submitting" @click="handleConfirm">确定</el-button>
    </template>
  </el-dialog>
</template>

<script setup>
import { ref, reactive, nextTick } from 'vue';

const dialogVisible = ref(false);
const submitting = ref(false);
const isEdit = ref(false);
const formRef = ref(null);
const form = reactive({ name: '' });

// 打开新增
const handleAdd = () => {
  isEdit.value = false;
  dialogVisible.value = true;
  // 如果用 resetFields,需等 DOM 更新
  nextTick(() => formRef.value?.resetFields());
};

// 打开编辑
const handleEdit = (row) => {
  isEdit.value = true;
  dialogVisible.value = true;
  nextTick(() => {
    Object.assign(form, row);
  });
};

// 确定
const handleConfirm = async () => {
  await formRef.value.validate();
  submitting.value = true;
  try {
    isEdit.value ? await api.update(form) : await api.create(form);
    ElMessage.success(isEdit.value ? '编辑成功' : '新增成功');
    dialogVisible.value = false;
    emit('success');
  } finally {
    submitting.value = false;
  }
};

// 关闭后清理
const handleClosed = () => {
  formRef.value?.resetFields();
};
</script>

Dialog 常用属性速查

属性

说明

默认值

v-model

显示/隐藏

false

title

标题

""

width

宽度

"50%"

fullscreen

全屏

false

top

距顶部距离

"15vh"

modal

显示遮罩层

true

close-on-click-modal

点击遮罩关闭

true

close-on-press-escape

按 ESC 关闭

true

show-close

显示关闭按钮

true

draggable

可拖拽

false

destroy-on-close

关闭时销毁内容

false

@open / @opened / @close / @closed

打开/关闭事件

-

4.2 Drawer(抽屉)

<template>
  <el-button @click="drawerVisible = true">打开抽屉</el-button>

  <el-drawer
    v-model="drawerVisible"
    title="详情"
    direction="rtl"    <!-- rtl(右)/ltr(左)/ttb(上)/btt(下) -->
    size="40%">
    <p>抽屉内容...</p>
  </el-drawer>
</template>

4.3 Popover / Tooltip(气泡提示)

<!-- Popover:点击触发,内容丰富 -->
<el-popover placement="bottom" :width="300" trigger="click">
  <template #reference>
    <el-button>点击弹出</el-button>
  </template>
  <p>可以放表格、表单等任何内容</p>
</el-popover>

<!-- Tooltip:hover 触发,纯文本 -->
<el-tooltip content="这是提示文字" placement="top">
  <el-button>悬停查看</el-button>
</el-tooltip>

五、消息提示与反馈

5.1 Message(消息提示)

import { ElMessage } from 'element-plus';

// 四种类型
ElMessage.success('操作成功');
ElMessage.warning('警告信息');
ElMessage.error('操作失败');
ElMessage.info('普通信息');

// 配置选项
ElMessage({
  type: 'success',
  message: '这是一条不会自动关闭的消息',
  duration: 0,           // 0 = 不自动关闭
  showClose: true,       // 显示关闭按钮
  center: true,          // 文字居中
  offset: 100,           // 距顶偏移

  // 自定义关闭回调
  onClose: () => { console.log('消息关闭了'); }
});

5.2 MessageBox(确认框)

import { ElMessageBox } from 'element-plus';

// 确认框
ElMessageBox.confirm('确定删除该记录?', '警告', {
  confirmButtonText: '确定',
  cancelButtonText: '取消',
  type: 'warning',
}).then(() => {
  // 点击确定
}).catch(() => {
  // 点击取消(catch 是取消,不是异常!)
});

// 提示框
ElMessageBox.alert('操作不可逆!', '注意', { confirmButtonText: '知道了' });

// 输入框
ElMessageBox.prompt('请输入备注', '提示', {
  inputValidator: (val) => val ? true : '不能为空'
}).then(({ value }) => {
  console.log('输入内容:', value);
});

// 自定义内容
ElMessageBox({
  title: '确认提交',
  message: h('p', null, [
    h('span', null, '内容可以是 '),
    h('span', { style: 'color: red' }, 'VNode'),
  ]),
  showCancelButton: true,
});

5.3 Notification(通知)

import { ElNotification } from 'element-plus';

ElNotification({
  title: '新消息',
  message: '您有一条新的系统通知',
  type: 'success',       // success / warning / info / error
  duration: 4500,
  position: 'top-right', // top-right / top-left / bottom-right / bottom-left
});

5.4 Loading(加载)

import { ElLoading } from 'element-plus';

// 全屏 loading
const loading = ElLoading.service({ text: '加载中...', fullscreen: true });
await doSomething();
loading.close();

// 局部 loading(配合 el-table 的 v-loading 更快)
// <el-table v-loading="loading" ...>

六、布局与容器

6.1 Container 布局容器(后台管理经典布局)

<template>
  <el-container style="height: 100vh">
    <!-- 左侧菜单 -->
    <el-aside width="200px">
      <el-menu :default-active="activeMenu" router background-color="#304156" text-color="#bfcbd9" active-text-color="#409EFF">
        <el-menu-item index="/dashboard">
          <el-icon><HomeFilled /></el-icon>
          <span>首页</span>
        </el-menu-item>
        <el-sub-menu index="system">
          <template #title>
            <el-icon><Setting /></el-icon>
            <span>系统管理</span>
          </template>
          <el-menu-item index="/system/user">用户管理</el-menu-item>
          <el-menu-item index="/system/role">角色管理</el-menu-item>
        </el-sub-menu>
      </el-menu>
    </el-aside>

    <!-- 右侧主体 -->
    <el-container>
      <!-- 顶部导航 -->
      <el-header height="60px" style="border-bottom: 1px solid #eee">
        <div class="header-content">
          <span>后台管理系统</span>
          <el-dropdown>
            <span class="user-info">管理员 ▼</span>
            <template #dropdown>
              <el-dropdown-menu>
                <el-dropdown-item>个人信息</el-dropdown-item>
                <el-dropdown-item divided @click="logout">退出登录</el-dropdown-item>
              </el-dropdown-menu>
            </template>
          </el-dropdown>
        </div>
      </el-header>

      <!-- 内容区 -->
      <el-main>
        <router-view />
      </el-main>
    </el-container>
  </el-container>
</template>

6.2 Tabs 标签页

<el-tabs v-model="activeTab" type="border-card" @tab-click="onTabClick">
  <el-tab-pane label="基本信息" name="basic">基本信息内容</el-tab-pane>
  <el-tab-pane label="高级设置" name="advanced">高级设置内容</el-tab-pane>
</el-tabs>

6.3 栅格布局(Row / Col)

<el-row :gutter="20">
  <el-col :span="6">
    <!-- 占 1/4 -->
    <el-card>统计卡片</el-card>
  </el-col>
  <el-col :span="6"><el-card>统计卡片</el-card></el-col>
  <el-col :span="12">
    <!-- 占 1/2 -->
    <el-card>图表</el-card>
  </el-col>
</el-row>

响应式断点:xs(&lt;768) / sm(≥768) / md(≥992) / lg(≥1200) / xl(≥1920)

<el-col :xs="24" :sm="12" :md="8" :lg="6">
  <!-- 手机全宽 → 平板半宽 → 桌面1/4 -->
</el-col>

七、常用组件速查与完整示例

7.1 按钮 el-button

<!-- 类型 -->
<el-button>默认</el-button>
<el-button type="primary">主要</el-button>
<el-button type="success">成功</el-button>
<el-button type="warning">警告</el-button>
<el-button type="danger">危险</el-button>
<el-button type="info">信息</el-button>

<!-- 状态 -->
<el-button :loading="true">加载中</el-button>
<el-button :disabled="true">禁用</el-button>

<!-- 样式 -->
<el-button plain>朴素</el-button>
<el-button round>圆角</el-button>
<el-button circle><el-icon><Search /></el-icon></el-button>
<el-button size="large">大</el-button>
<el-button size="small">小</el-button>

<!-- 图标 -->
<el-button type="primary"><el-icon style="margin-right:6px"><Plus /></el-icon>新增</el-button>

7.2 标签 el-tag

<el-tag>默认</el-tag>
<el-tag type="success">完成</el-tag>
<el-tag type="warning">待处理</el-tag>
<el-tag type="danger">失败</el-tag>
<el-tag type="info">已取消</el-tag>

<!-- 可关闭 -->
<el-tag closable @close="handleClose">可关闭标签</el-tag>

<!-- 效果 -->
<el-tag effect="dark">深色</el-tag>
<el-tag effect="plain">浅色</el-tag>

<!-- 圆形 -->
<el-tag round>圆角</el-tag>

<!-- 尺寸 -->
<el-tag size="large">大号</el-tag>

7.3 进度条 el-progress

<el-progress :percentage="70" />
<el-progress :percentage="100" status="success" />
<el-progress :percentage="50" status="exception" />
<el-progress :percentage="30" :stroke-width="20" :text-inside="true" />
<!-- 环形 -->
<el-progress type="circle" :percentage="80" :width="120" />
<!-- 仪表盘 -->
<el-progress type="dashboard" :percentage="75" />

7.4 头像 el-avatar

<el-avatar :size="50" src="https://..." />
<el-avatar :size="40" @error="errorHandler">
  <img src="fallback.jpg" />
</el-avatar>
<!-- 文字头像 -->
<el-avatar>Admin</el-avatar>

7.5 卡片 el-card

<el-card shadow="hover">
  <template #header>
    <span>卡片标题</span>
  </template>
  <p>卡片内容</p>
</el-card>

7.6 折叠面板 el-collapse

<el-collapse v-model="activeNames" accordion>
  <el-collapse-item title="一致性" name="1">
    <p>与现实生活一致:与现实生活的流程、逻辑保持一致,遵循用户习惯的语言和概念</p>
  </el-collapse-item>
  <el-collapse-item title="反馈" name="2">
    <p>控制反馈:通过界面样式和交互动效让用户可以清晰的感知自己的操作</p>
  </el-collapse-item>
</el-collapse>

7.7 树形控件 el-tree

<template>
  <el-tree
    :data="treeData"
    node-key="id"
    :props="{ children: 'children', label: 'name' }"
    default-expand-all
    show-checkbox
    check-strictly
    highlight-current
    @node-click="onNodeClick"
    @check="onCheck" />
</template>

<script setup>
const treeData = [
  {
    id: 1, name: '一级菜单',
    children: [
      { id: 11, name: '子菜单 1' },
      { id: 12, name: '子菜单 2' },
    ]
  }
];

// 获取选中节点
const getCheckedKeys = () => treeRef.value?.getCheckedKeys();
const getCheckedNodes = () => treeRef.value?.getCheckedNodes();
const setCheckedKeys = (keys) => treeRef.value?.setCheckedKeys(keys);

// 过滤/筛选节点
const filterNode = (value, data) => data.name.includes(value);
// <el-tree :filter-node-method="filterNode" ref="treeRef" ... />
</script>

7.8 步骤条 el-steps

<el-steps :active="step" align-center finish-status="success">
  <el-step title="步骤 1" description="填写信息" />
  <el-step title="步骤 2" description="验证信息" />
  <el-step title="步骤 3" description="完成" />
</el-steps>

7.9 时间线 el-timeline

<el-timeline>
  <el-timeline-item timestamp="2024-01-01" placement="top">
    <el-card><p>创建项目</p></el-card>
  </el-timeline-item>
  <el-timeline-item timestamp="2024-01-15" placement="top">
    <el-card><p>发布 v1.0</p></el-card>
  </el-timeline-item>
  <el-timeline-item timestamp="2024-02-01" placement="top" type="success">
    <el-card><p>完成验收</p></el-card>
  </el-timeline-item>
</el-timeline>
<el-carousel :interval="4000" type="card" height="200px">
  <el-carousel-item v-for="i in 4" :key="i">
    <h3>{{ i }}</h3>
  </el-carousel-item>
</el-carousel>

7.11 描述列表 el-descriptions

<el-descriptions :column="2" border>
  <el-descriptions-item label="用户名" :span="1">admin</el-descriptions-item>
  <el-descriptions-item label="手机号">13800138000</el-descriptions-item>
  <el-descriptions-item label="角色" :span="2">超级管理员</el-descriptions-item>
  <el-descriptions-item label="备注">
    <el-tag size="small">学校</el-tag>
  </el-descriptions-item>
</el-descriptions>

7.12 空状态 el-empty

<el-empty description="暂无数据" :image-size="200" />
<!-- 自定义图片 -->
<el-empty description="暂无订单">
  <el-button type="primary">去下单</el-button>
</el-empty>

7.13 骨架屏 el-skeleton

<el-skeleton :rows="5" animated :loading="loading">
  <template #default>
    <p>实际内容...</p>
  </template>
</el-skeleton>

<!-- 详细骨架 -->
<el-skeleton>
  <template #template>
    <el-skeleton-item variant="image" style="width: 240px; height: 240px" />
    <div style="padding: 14px">
      <el-skeleton-item variant="h3" />
      <el-skeleton-item variant="text" style="margin-top: 16px" />
    </div>
  </template>
</el-skeleton>

7.14 回到顶部 el-backtop

<el-backtop :bottom="100" :visibility-height="300" />

八、后台管理常用组合套路

8.1 增删改查页面模板

<template>
  <div class="page-container">
    <!-- 1. 搜索区域 -->
    <el-card class="search-card">
      <el-form :model="query" inline>
        <el-form-item label="关键词"><el-input v-model="query.keyword" clearable placeholder="请输入" /></el-form-item>
        <el-form-item label="状态">
          <el-select v-model="query.status" clearable placeholder="请选择">
            <el-option label="启用" :value="1" />
            <el-option label="禁用" :value="0" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="search">搜索</el-button>
          <el-button @click="reset">重置</el-button>
        </el-form-item>
      </el-form>
    </el-card>

    <!-- 2. 操作栏 -->
    <el-card class="tool-card">
      <el-button type="primary" @click="handleAdd"><el-icon><Plus /></el-icon>新增</el-button>
      <el-button type="danger" :disabled="!selected.length" @click="batchDelete">批量删除</el-button>
      <el-button @click="exportData"><el-icon><Download /></el-icon>导出</el-button>
    </el-card>

    <!-- 3. 数据表格 -->
    <el-card class="table-card">
      <el-table :data="tableData" border stripe v-loading="loading"
        @selection-change="(rows) => selected = rows">
        <el-table-column type="selection" width="50" />
        <el-table-column type="index" label="#" width="60" />
        <el-table-column prop="name" label="名称" />
        <el-table-column prop="status" label="状态" width="100">
          <template #default="{ row }">
            <el-tag :type="row.status === 1 ? 'success' : 'danger'">
              {{ row.status === 1 ? '启用' : '禁用' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="180" fixed="right">
          <template #default="{ row }">
            <el-button size="small" @click="handleEdit(row)">编辑</el-button>
            <el-button size="small" type="danger" @click="handleDelete(row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <el-pagination v-model:current-page="pagination.page" v-model:page-size="pagination.size"
        :total="pagination.total" :page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next" background />
    </el-card>

    <!-- 4. 新增/编辑弹窗 -->
    <el-dialog v-model="dialogVisible" :title="isEdit ? '编辑' : '新增'" width="600px" @closed="onClosed">
      <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
        <el-form-item label="名称" prop="name">
          <el-input v-model="form.name" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="dialogVisible = false">取消</el-button>
        <el-button type="primary" :loading="submitting" @click="submit">确定</el-button>
      </template>
    </el-dialog>
  </div>
</template>

8.2 全局确认删除 Hook(封装复用)

// composables/useDelete.js
import { ElMessage, ElMessageBox } from 'element-plus';

export function useDelete(api, options = {}) {
  const { message = '确认删除?', successMsg = '删除成功' } = options;

  const confirm = (idOrRow, onSuccess) => {
    ElMessageBox.confirm(message, '提示', {
      type: 'warning',
      confirmButtonText: '确定',
    }).then(async () => {
      const id = typeof idOrRow === 'object' ? idOrRow.id : idOrRow;
      await api(id);
      ElMessage.success(successMsg);
      onSuccess?.();
    }).catch(() => {});  // 取消操作
  };

  return { confirm };
}

// 使用
const { confirm: deleteUser } = useDelete(api.deleteUser);
deleteUser(row, () => fetchData());

附录:后台开发最常用 Top 8

排名

组件

使用频率

关键场景

1

el-form + el-input + 校验

100%

所有新增/编辑/搜索

2

el-table + el-pagination

100%

所有列表页

3

el-dialog

90%

新增/编辑弹窗

4

ElMessage / ElMessageBox

90%

操作反馈

5

el-select + el-option

85%

下拉选择/筛选

6

el-date-picker

80%

日期筛选/选择

7

el-button

100%

所有操作入口

8

el-container + el-menu

70%

布局框架

学习建议:先精通 Top 5(表单、表格、弹窗、消息提示、下拉选择),再逐一覆盖其他组件。每个组件优先看官方文档的 Demo,再对照本文的完整示例理解实际业务场景中的用法。