外观
Element Plus
2026-04-01
版本: 2.8.x 官网: element-plus.org
Element Plus 是 Nebula 前端的核心 UI 组件库,提供表单、表格、弹窗、按钮、消息提示等常用组件。
项目中已通过 unplugin-auto-import + unplugin-vue-components 实现按需自动导入,无需手动 import。
安装与注册
子应用 main.ts 中全量注册(已配置中文 locale):
import ElementPlus from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import 'element-plus/dist/index.css'
app.use(ElementPlus, { locale: zhCn })消息提示
ElMessage 和 ElMessageBox 是使用频率最高的反馈组件:
import { ElMessage, ElMessageBox } from 'element-plus'
// 轻提示(成功 / 警告 / 错误 / 信息)
ElMessage.success('操作成功')
ElMessage.warning('请先选择数据')
ElMessage.error('服务器异常,请稍后重试')
ElMessage({ message: '自定义提示', duration: 3000 })
// 确认弹窗(异步,等待用户操作)
async function handleDelete(id: number) {
try {
await ElMessageBox.confirm('确认删除该记录?删除后不可恢复', '提示', {
confirmButtonText: '确认删除',
cancelButtonText: '取消',
type: 'warning',
})
await deleteApi(id)
ElMessage.success('删除成功')
loadData()
} catch {
// 用户点取消,catch 到 'cancel' 字符串,不做处理
}
}表单 & 校验
<template>
<el-form ref="formRef" :model="form" :rules="rules" label-width="90px">
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="form.phone" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="角色" prop="roleIds">
<el-select v-model="form.roleIds" multiple placeholder="请选择角色" style="width: 100%">
<el-option v-for="role in roleOptions" :key="role.id" :label="role.roleName" :value="role.id" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="saving" @click="onSubmit">保存</el-button>
<el-button @click="onCancel">取消</el-button>
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
const formRef = ref<FormInstance>()
const saving = ref(false)
const form = ref({
username: '',
phone: '',
roleIds: [] as number[],
})
const rules: FormRules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 2, max: 20, message: '用户名长度 2~20 个字符', trigger: 'blur' },
],
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' },
],
}
async function onSubmit() {
const valid = await formRef.value?.validate().catch(() => false)
if (!valid) return
saving.value = true
try {
await saveApi(form.value)
ElMessage.success('保存成功')
} finally {
saving.value = false
}
}
function onCancel() {
formRef.value?.resetFields()
}
</script>表格
Nebula 项目中常见表格形态:
<template>
<!-- 基础表格 -->
<el-table v-loading="loading" :data="tableData" border stripe>
<el-table-column type="index" label="#" width="50" />
<el-table-column prop="username" label="用户名" min-width="120" />
<el-table-column prop="phone" label="手机号" width="130" />
<el-table-column prop="status" label="状态" width="90">
<template #default="{ row }">
<el-tag :type="row.status === 0 ? 'success' : 'danger'">
{{ row.status === 0 ? '正常' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="140" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" link @click="handleDelete(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="page.current"
v-model:page-size="page.size"
:total="page.total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@size-change="loadData"
@current-change="loadData"
/>
</template>弹窗 / 抽屉
<template>
<!-- 弹窗:适合小表单 -->
<el-dialog v-model="dialogVisible" title="新增用户" width="560px" :close-on-click-modal="false">
<UserForm ref="formRef" :data="currentRow" />
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="onConfirm">确认</el-button>
</template>
</el-dialog>
<!-- 抽屉:适合大表单或详情展示 -->
<el-drawer v-model="drawerVisible" title="用户详情" size="600px" direction="rtl">
<UserDetail :id="currentId" />
</el-drawer>
</template>图标使用
项目引入了 @element-plus/icons-vue,可直接在模板中使用:
<script setup lang="ts">
import { Plus, Delete, Edit, Search, Refresh } from '@element-plus/icons-vue'
</script>
<template>
<el-button type="primary" :icon="Plus" @click="handleAdd">新增</el-button>
<el-button :icon="Refresh" circle @click="loadData" />
<el-input :prefix-icon="Search" placeholder="搜索关键词" />
</template>常用技巧
Switch 状态更新(带 loading)
<el-switch
:model-value="row.status === 0"
inline-prompt
:loading="Boolean(statusSaving[row.id])"
@change="onStatusChange($event, row)"
/>
<script setup lang="ts">
const statusSaving = ref<Record<number, boolean>>({})
async function onStatusChange(val: boolean, row: any) {
statusSaving.value[row.id] = true
try {
await updateStatusApi(row.id, val ? 0 : 1)
row.status = val ? 0 : 1
ElMessage.success('状态更新成功')
} catch {
// 失败时 switch 恢复原状(不修改 row.status)
} finally {
delete statusSaving.value[row.id]
}
}
</script>Tooltip 超长内容
<el-table-column prop="remark" label="备注" show-overflow-tooltip />常见问题
| 问题 | 解决方案 |
|---|---|
| 弹窗点击遮罩关闭触发校验 | 设置 :close-on-click-modal="false" |
el-select 值为对象但匹配不上 | :value-key="'id'" 指定唯一键 |
| 表单重置无效 | 使用 formRef.value?.resetFields(),不要手动赋值 {} |
| 表格列宽不生效 | 父容器需有明确宽度,或设置 min-width 代替 width |
