Vue3(篇三) Element Plus

目录

  • 前言
  • [一、Element Plus 是什么?](#一、Element Plus 是什么?)
    • [1.1 UI 组件库的作用](#1.1 UI 组件库的作用)
    • [1.2 为什么选择 Element Plus?](#1.2 为什么选择 Element Plus?)
  • 二、安装与配置
    • [2.1 安装](#2.1 安装)
    • [2.2 完整引入](#2.2 完整引入)
    • [2.3 按需引入(推荐)](#2.3 按需引入(推荐))
  • 三、表格组件
    • [3.1 基础表格](#3.1 基础表格)
    • [3.2 自定义列内容](#3.2 自定义列内容)
    • [3.3 加载状态](#3.3 加载状态)
  • 四、分页组件
    • [4.1 基础分页](#4.1 基础分页)
    • [4.2 后端分页(真实场景)](#4.2 后端分页(真实场景))
  • 五、搜索与删除
    • [5.1 搜索功能](#5.1 搜索功能)
    • [5.2 删除功能](#5.2 删除功能)
    • [5.3 批量删除](#5.3 批量删除)
  • 六、新增与修改
    • [6.1 对话框组件](#6.1 对话框组件)
    • [6.2 表单组件](#6.2 表单组件)
    • [6.3 表单回显与重置](#6.3 表单回显与重置)
  • 七、子组件通信
    • [7.1 定义子组件](#7.1 定义子组件)
    • [7.2 父组件使用子组件](#7.2 父组件使用子组件)
    • [7.3 父子组件通信总结](#7.3 父子组件通信总结)
  • 八、完整案例:学生管理页面
  • 总结

前言

与后端交互:axios与请求封装中,我们掌握了前端与后端 API 交互的方式。现在数据能获取到了,但如何优雅地展示和管理这些数据呢?

直接用原生 HTML 标签(table、form)可以实现功能,但样式简陋、交互生硬,还需要自己写大量 CSS 和 JavaScript。在实际开发中,我们通常使用 UI 组件库 来快速构建美观、交互友好的界面。

Element Plus 是 Vue3 生态中最成熟的 UI 组件库,提供了表格、表单、对话框、分页等常用组件,开箱即用。

本文将通过一个学生管理系统案例,讲解 Element Plus 的核心组件和实战技巧,包括表格、分页、搜索、增删改、表单校验、子组件通信等。

一、Element Plus 是什么?

1.1 UI 组件库的作用

如果把开发比作装修:

  • 原生 HTML/CSS 是自己买材料、自己设计、自己施工
  • UI 组件库 是精装修套餐------样式美观、交互完善、开箱即用

组件库提供的典型组件:

类别 组件 作用
基础 Button、Icon、Link 按钮、图标、链接
表单 Input、Select、Checkbox、Radio、Switch 各类输入控件
数据 Table、Pagination、Tree、Tag 数据展示
反馈 Message、Dialog、Notification、Loading 提示反馈
导航 Menu、Tabs、Breadcrumb 页面导航
布局 Container、Layout、Grid 页面布局

1.2 为什么选择 Element Plus?

优势 说明
Vue3 原生支持 基于 Vue3 开发,完美支持组合式 API
组件丰富 60+ 组件,覆盖绝大多数场景
文档完善 中文文档详细,示例丰富
生态成熟 社区活跃,问题容易找到解决方案
企业级 饿了么团队开发,经受过大规模项目验证

类比 Java:Element Plus 类似于 Java 的前端 UI 框架,如 Apache Wicket 或 JSF 组件库,但更现代化。

二、安装与配置

2.1 安装

bash 复制代码
npm install element-plus

2.2 完整引入

main.ts 中引入 Element Plus 及其样式:

typescript 复制代码
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')

2.3 按需引入(推荐)

完整引入会导致打包体积较大。推荐使用按需引入,只打包用到的组件。

安装自动导入插件:

bash 复制代码
npm install -D unplugin-vue-components unplugin-auto-import

配置 vite.config.ts

typescript 复制代码
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,直接使用组件即可:

vue 复制代码
<template>
  <!-- 自动导入,无需 import -->
  <el-button type="primary">按钮</el-button>
</template>

三、表格组件

表格是后台管理系统最常用的组件。Element Plus 的 el-table 功能强大,支持排序、筛选、固定列、自定义列等。

3.1 基础表格

vue 复制代码
<script setup lang="ts">
import { ref } from 'vue'

interface Student {
  id: number
  name: string
  sex: string
  age: number
}

const students = ref<Student[]>([
  { id: 1, name: '张三', sex: '男', age: 18 },
  { id: 2, name: '李四', sex: '女', age: 19 },
  { id: 3, name: '王五', sex: '男', age: 20 }
])
</script>

<template>
  <el-table :data="students" border stripe style="width: 100%">
    <el-table-column prop="id" label="编号" width="100" />
    <el-table-column prop="name" label="姓名" />
    <el-table-column prop="sex" label="性别" />
    <el-table-column prop="age" label="年龄" />
  </el-table>
</template>
属性 说明
:data 表格数据(数组)
border 显示边框
stripe 斑马纹
prop 列对应的字段名
label 列标题
width 列宽度

3.2 自定义列内容

有时需要在列中显示复杂内容(如按钮、格式化文本):

vue 复制代码
<template>
  <el-table :data="students" border stripe>
    <el-table-column prop="id" label="编号" width="100" />
    <el-table-column prop="name" label="姓名">
      <!-- 自定义列:通过 #default 获取当前行数据 -->
      <template #default="{ row }">
        <span :style="{ color: row.sex === '女' ? 'pink' : 'blue' }">
          {{ row.name }}
        </span>
      </template>
    </el-table-column>
    <el-table-column prop="sex" label="性别" />
    <el-table-column label="操作">
      <template #default="{ row }">
        <el-button type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
        <el-button type="danger" size="small" @click="handleDelete(row.id)">删除</el-button>
      </template>
    </el-table-column>
  </el-table>
</template>

<script setup lang="ts">
function handleEdit(row: Student) {
  console.log('编辑:', row)
}

function handleDelete(id: number) {
  console.log('删除:', id)
}
</script>

重要#default="{ row }" 是插槽语法,row 是当前行的数据对象。

3.3 加载状态

从后端获取数据时,显示加载状态:

vue 复制代码
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import request from '@/api/request'

const students = ref<Student[]>([])
const loading = ref(false)

async function getStudents() {
  loading.value = true
  try {
    const data = await request.get<Student[]>('/api/students')
    students.value = data
  } finally {
    loading.value = false
  }
}

onMounted(() => {
  getStudents()
})
</script>

<template>
  <el-table :data="students" border stripe v-loading="loading">
    <el-table-column prop="id" label="编号" />
    <el-table-column prop="name" label="姓名" />
    <!-- ... -->
  </el-table>
</template>

四、分页组件

数据量大时,需要分页展示。Element Plus 的 el-pagination 与表格配合使用。

4.1 基础分页

vue 复制代码
<script setup lang="ts">
import { ref, computed } from 'vue'

const allStudents = ref<Student[]>([
  // 假设有 100 条数据...
])

const currentPage = ref(1)
const pageSize = ref(10)

// 当前页数据
const currentPageData = computed(() => {
  const start = (currentPage.value - 1) * pageSize.value
  const end = start + pageSize.value
  return allStudents.value.slice(start, end)
})

function handlePageChange(page: number) {
  currentPage.value = page
}

function handleSizeChange(size: number) {
  pageSize.value = size
  currentPage.value = 1  // 切换每页条数时,重置到第一页
}
</script>

<template>
  <el-table :data="currentPageData" border stripe>
    <!-- 列定义... -->
  </el-table>
  
  <el-pagination
    v-model:current-page="currentPage"
    v-model:page-size="pageSize"
    :page-sizes="[10, 20, 50, 100]"
    :total="allStudents.length"
    layout="total, sizes, prev, pager, next, jumper"
    @size-change="handleSizeChange"
    @current-change="handlePageChange"
  />
</template>
属性/事件 说明
v-model:current-page 当前页码
v-model:page-size 每页条数
:page-sizes 每页条数选项
:total 总记录数
layout 分页布局元素
@size-change 每页条数改变事件
@current-change 页码改变事件

4.2 后端分页(真实场景)

实际项目中,分页通常由后端实现,前端只传递分页参数:

vue 复制代码
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRequest } from 'vue-request'
import request from '@/api/request'

interface PageResult<T> {
  list: T[]
  total: number
}

const queryParams = ref({
  name: '',
  page: 1,
  size: 10
})

const { data, total, loading, run } = useRequest<PageResult<Student>>(
  () => request.get('/api/students/page', { params: queryParams.value }),
  {
    defaultParams: [queryParams.value],
    pagination: {
      currentKey: 'page',
      pageSizeKey: 'size',
      totalKey: 'data.total'
    }
  }
)

function handlePageChange(page: number) {
  queryParams.value.page = page
  run(queryParams.value)
}

function handleSizeChange(size: number) {
  queryParams.value.size = size
  queryParams.value.page = 1
  run(queryParams.value)
}
</script>

<template>
  <el-table :data="data?.list || []" border stripe v-loading="loading">
    <el-table-column prop="id" label="编号" />
    <el-table-column prop="name" label="姓名" />
    <!-- ... -->
  </el-table>
  
  <el-pagination
    v-model:current-page="queryParams.page"
    v-model:page-size="queryParams.size"
    :page-sizes="[10, 20, 50]"
    :total="total"
    layout="total, sizes, prev, pager, next"
    @size-change="handleSizeChange"
    @current-change="handlePageChange"
  />
</template>

五、搜索与删除

5.1 搜索功能

vue 复制代码
<script setup lang="ts">
import { ref } from 'vue'

const queryParams = ref({
  name: '',
  sex: '',
  ageRange: ''
})

const { data, loading, run } = useRequest(
  () => request.get('/api/students/page', { params: queryParams.value })
)

function handleSearch() {
  queryParams.value.page = 1  // 搜索时重置到第一页
  run()
}
</script>

<template>
  <div class="search-bar">
    <el-input
      v-model="queryParams.name"
      placeholder="姓名"
      style="width: 150px"
      clearable
    />
    
    <el-select
      v-model="queryParams.sex"
      placeholder="性别"
      style="width: 100px"
      clearable
    >
      <el-option label="男" value="男" />
      <el-option label="女" value="女" />
    </el-select>
    
    <el-select
      v-model="queryParams.ageRange"
      placeholder="年龄"
      style="width: 120px"
      clearable
    >
      <el-option label="18岁以下" value="0,18" />
      <el-option label="18-25岁" value="18,25" />
      <el-option label="25岁以上" value="25,100" />
    </el-select>
    
    <el-button type="primary" @click="handleSearch">搜索</el-button>
  </div>
  
  <el-table :data="data?.list || []" border stripe v-loading="loading">
    <!-- 列定义... -->
  </el-table>
</template>

<style scoped>
.search-bar {
  margin-bottom: 16px;
  display: flex;
  gap: 8px;
}
</style>

5.2 删除功能

使用 ElMessageBox 确认删除:

vue 复制代码
<script setup lang="ts">
import { ElMessage, ElMessageBox } from 'element-plus'

async function handleDelete(id: number) {
  try {
    // 确认框
    await ElMessageBox.confirm('确认删除该学生?', '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
    
    // 调用删除接口
    await request.delete(`/api/students/${id}`)
    
    // 提示成功
    ElMessage.success('删除成功')
    
    // 刷新列表
    run()
  } catch (error) {
    // 用户点击取消,不做处理
    if (error === 'cancel') return
    ElMessage.error('删除失败')
  }
}
</script>

5.3 批量删除

vue 复制代码
<script setup lang="ts">
import { ref } from 'vue'

const selectedIds = ref<number[]>([])

// 表格选择变化事件
function handleSelectionChange(selection: Student[]) {
  selectedIds.value = selection.map(s => s.id)
}

async function handleBatchDelete() {
  if (selectedIds.value.length === 0) {
    ElMessage.warning('请先选择要删除的学生')
    return
  }
  
  try {
    await ElMessageBox.confirm(`确认删除选中的 ${selectedIds.value.length} 名学生?`, '提示')
    await request.delete('/api/students', { data: selectedIds.value })
    ElMessage.success('删除成功')
    selectedIds.value = []
    run()
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error('删除失败')
    }
  }
}
</script>

<template>
  <el-button type="danger" @click="handleBatchDelete">批量删除</el-button>
  
  <el-table
    :data="data?.list || []"
    border
    stripe
    @selection-change="handleSelectionChange"
  >
    <!-- 多选列 -->
    <el-table-column type="selection" width="55" />
    <el-table-column prop="id" label="编号" />
    <!-- ... -->
  </el-table>
</template>

六、新增与修改

新增和修改通常使用对话框(Dialog)+ 表单(Form)实现。

6.1 对话框组件

vue 复制代码
<script setup lang="ts">
import { ref } from 'vue'

const dialogVisible = ref(false)
const dialogTitle = ref('新增学生')

// 打开新增对话框
function handleAdd() {
  dialogTitle.value = '新增学生'
  dialogVisible.value = true
}

// 打开编辑对话框
function handleEdit(row: Student) {
  dialogTitle.value = '编辑学生'
  // 回填表单数据...
  dialogVisible.value = true
}
</script>

<template>
  <el-button type="primary" @click="handleAdd">新增学生</el-button>
  
  <el-dialog
    v-model="dialogVisible"
    :title="dialogTitle"
    width="500px"
  >
    <!-- 表单内容 -->
    <span>这里是表单...</span>
    
    <template #footer>
      <el-button @click="dialogVisible = false">取消</el-button>
      <el-button type="primary" @click="handleSubmit">确定</el-button>
    </template>
  </el-dialog>
</template>

6.2 表单组件

vue 复制代码
<script setup lang="ts">
import { ref, reactive } from 'vue'

interface StudentForm {
  id?: number
  name: string
  sex: string
  age: number
}

const form = reactive<StudentForm>({
  name: '',
  sex: '男',
  age: 18
})

const formRef = ref()

const rules = {
  name: [
    { required: true, message: '请输入姓名', trigger: 'blur' },
    { min: 2, max: 10, message: '长度在 2 到 10 个字符', trigger: 'blur' }
  ],
  sex: [
    { required: true, message: '请选择性别', trigger: 'change' }
  ],
  age: [
    { required: true, message: '请输入年龄', trigger: 'blur' },
    { type: 'number', min: 1, max: 150, message: '年龄在 1 到 150 之间', trigger: 'blur' }
  ]
}

async function handleSubmit() {
  try {
    // 表单校验
    await formRef.value.validate()
    
    // 提交数据
    if (form.id) {
      await request.put(`/api/students/${form.id}`, form)
      ElMessage.success('修改成功')
    } else {
      await request.post('/api/students', form)
      ElMessage.success('新增成功')
    }
    
    dialogVisible.value = false
    run()  // 刷新列表
  } catch (error) {
    // 校验失败,不做处理
  }
}
</script>

<template>
  <el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px">
    <el-form
      ref="formRef"
      :model="form"
      :rules="rules"
      label-width="80px"
    >
      <el-form-item label="姓名" prop="name">
        <el-input v-model="form.name" />
      </el-form-item>
      
      <el-form-item label="性别" prop="sex">
        <el-radio-group v-model="form.sex">
          <el-radio value="男">男</el-radio>
          <el-radio value="女">女</el-radio>
        </el-radio-group>
      </el-form-item>
      
      <el-form-item label="年龄" prop="age">
        <el-input-number v-model="form.age" :min="1" :max="150" />
      </el-form-item>
    </el-form>
    
    <template #footer>
      <el-button @click="dialogVisible = false">取消</el-button>
      <el-button type="primary" @click="handleSubmit">确定</el-button>
    </template>
  </el-dialog>
</template>
Element Plus 说明 类比 Java
:model 表单数据对象 JavaBean
:rules 校验规则 @NotNull@Size 等注解
prop 字段名(与 rules 对应) 属性名
trigger 触发时机 -
validate() 执行校验 手动校验

6.3 表单回显与重置

编辑时需要回显数据,新增/编辑后需要重置表单:

vue 复制代码
<script setup lang="ts">
import { ref, reactive, nextTick } from 'vue'

const form = reactive<StudentForm>({
  name: '',
  sex: '男',
  age: 18
})

const formRef = ref()

// 打开新增对话框
function handleAdd() {
  dialogTitle.value = '新增学生'
  resetForm()
  dialogVisible.value = true
}

// 打开编辑对话框
async function handleEdit(row: Student) {
  dialogTitle.value = '编辑学生'
  
  // 先打开对话框,再回填数据(避免校验立即触发)
  dialogVisible.value = true
  
  // 等待 DOM 更新后再回填
  await nextTick()
  Object.assign(form, row)
}

// 重置表单
function resetForm() {
  formRef.value?.resetFields()
  // 或者手动重置
  Object.assign(form, {
    name: '',
    sex: '男',
    age: 18
  })
}

// 提交成功后
async function handleSubmit() {
  // ...提交逻辑...
  
  dialogVisible.value = false
  resetForm()  // 重置表单
  run()
}
</script>

七、子组件通信

当对话框表单逻辑复杂时,建议将表单抽取为子组件。

7.1 定义子组件

新建 src/components/StudentForm.vue

vue 复制代码
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
import type { FormInstance } from 'element-plus'

interface StudentForm {
  id?: number
  name: string
  sex: string
  age: number
}

// 接收父组件传递的属性
const props = defineProps<{
  modelValue: boolean    // 对话框显示状态
  student?: StudentForm  // 编辑时传入的学生数据
}>()

// 定义事件
const emit = defineEmits<{
  'update:modelValue': [value: boolean]
  'success': []
}>()

const formRef = ref<FormInstance>()
const form = reactive<StudentForm>({
  name: '',
  sex: '男',
  age: 18
})

const rules = {
  name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
  sex: [{ required: true, message: '请选择性别', trigger: 'change' }],
  age: [{ required: true, message: '请输入年龄', trigger: 'blur' }]
}

// 监听 student 变化,回填表单
watch(
  () => props.student,
  (newStudent) => {
    if (newStudent) {
      Object.assign(form, newStudent)
    } else {
      resetForm()
    }
  }
)

function resetForm() {
  formRef.value?.resetFields()
}

async function handleSubmit() {
  try {
    await formRef.value?.validate()
    
    if (form.id) {
      await request.put(`/api/students/${form.id}`, form)
      ElMessage.success('修改成功')
    } else {
      await request.post('/api/students', form)
      ElMessage.success('新增成功')
    }
    
    emit('update:modelValue', false)
    emit('success')
  } catch (error) {
    // 校验失败
  }
}

function handleCancel() {
  emit('update:modelValue', false)
  resetForm()
}
</script>

<template>
  <el-dialog
    :model-value="modelValue"
    :title="student ? '编辑学生' : '新增学生'"
    width="500px"
    @close="handleCancel"
  >
    <el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
      <el-form-item label="姓名" prop="name">
        <el-input v-model="form.name" />
      </el-form-item>
      
      <el-form-item label="性别" prop="sex">
        <el-radio-group v-model="form.sex">
          <el-radio value="男">男</el-radio>
          <el-radio value="女">女</el-radio>
        </el-radio-group>
      </el-form-item>
      
      <el-form-item label="年龄" prop="age">
        <el-input-number v-model="form.age" :min="1" :max="150" />
      </el-form-item>
    </el-form>
    
    <template #footer>
      <el-button @click="handleCancel">取消</el-button>
      <el-button type="primary" @click="handleSubmit">确定</el-button>
    </template>
  </el-dialog>
</template>

7.2 父组件使用子组件

vue 复制代码
<script setup lang="ts">
import { ref } from 'vue'
import StudentForm from '@/components/StudentForm.vue'

const dialogVisible = ref(false)
const currentStudent = ref<Student | undefined>()

// 新增
function handleAdd() {
  currentStudent.value = undefined
  dialogVisible.value = true
}

// 编辑
function handleEdit(row: Student) {
  currentStudent.value = { ...row }
  dialogVisible.value = true
}

// 表单提交成功
function handleFormSuccess() {
  run()  // 刷新列表
}
</script>

<template>
  <el-button type="primary" @click="handleAdd">新增学生</el-button>
  
  <el-table :data="data?.list || []" border stripe>
    <!-- ... -->
    <el-table-column label="操作">
      <template #default="{ row }">
        <el-button type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
      </template>
    </el-table-column>
  </el-table>
  
  <!-- 子组件 -->
  <StudentForm
    v-model="dialogVisible"
    :student="currentStudent"
    @success="handleFormSuccess"
  />
</template>

7.3 父子组件通信总结

方式 语法 说明
父传子 defineProps 父组件通过属性传递数据给子组件
子传父 defineEmits 子组件通过事件传递数据给父组件
双向绑定 v-model 父子组件同步数据(如对话框显示状态)

类比 Java:类似于 Java 的接口定义,父组件定义"接口"(props + events),子组件"实现"接口。

八、完整案例:学生管理页面

将以上内容整合成一个完整的学生管理页面:

vue 复制代码
<script setup lang="ts">
import { ref } from 'vue'
import { useRequest } from 'vue-request'
import { ElMessage, ElMessageBox } from 'element-plus'
import request from '@/api/request'
import StudentForm from '@/components/StudentForm.vue'

interface Student {
  id: number
  name: string
  sex: string
  age: number
}

// 查询参数
const queryParams = ref({
  name: '',
  sex: '',
  page: 1,
  size: 10
})

// 学生列表
const { data, total, loading, run } = useRequest(
  () => request.get('/api/students/page', { params: queryParams.value }),
  {
    defaultParams: [queryParams.value],
    pagination: {
      currentKey: 'page',
      pageSizeKey: 'size',
      totalKey: 'data.total'
    }
  }
)

// 搜索
function handleSearch() {
  queryParams.value.page = 1
  run()
}

// 分页
function handlePageChange(page: number) {
  queryParams.value.page = page
  run()
}

// 删除
async function handleDelete(id: number) {
  await ElMessageBox.confirm('确认删除?', '提示', { type: 'warning' })
  await request.delete(`/api/students/${id}`)
  ElMessage.success('删除成功')
  run()
}

// 对话框
const dialogVisible = ref(false)
const currentStudent = ref<Student>()

function handleAdd() {
  currentStudent.value = undefined
  dialogVisible.value = true
}

function handleEdit(row: Student) {
  currentStudent.value = { ...row }
  dialogVisible.value = true
}

function handleFormSuccess() {
  run()
}
</script>

<template>
  <div class="student-manager">
    <!-- 搜索栏 -->
    <div class="search-bar">
      <el-input v-model="queryParams.name" placeholder="姓名" clearable style="width: 150px" />
      <el-select v-model="queryParams.sex" placeholder="性别" clearable style="width: 100px">
        <el-option label="男" value="男" />
        <el-option label="女" value="女" />
      </el-select>
      <el-button type="primary" @click="handleSearch">搜索</el-button>
      <el-button type="primary" @click="handleAdd">新增</el-button>
    </div>
    
    <!-- 表格 -->
    <el-table :data="data?.list || []" border stripe v-loading="loading">
      <el-table-column prop="id" label="编号" width="100" />
      <el-table-column prop="name" label="姓名" />
      <el-table-column prop="sex" label="性别" />
      <el-table-column prop="age" label="年龄" />
      <el-table-column label="操作" width="150">
        <template #default="{ row }">
          <el-button type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
          <el-button type="danger" size="small" @click="handleDelete(row.id)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
    
    <!-- 分页 -->
    <el-pagination
      v-model:current-page="queryParams.page"
      v-model:page-size="queryParams.size"
      :total="total"
      :page-sizes="[10, 20, 50]"
      layout="total, sizes, prev, pager, next"
      @current-change="handlePageChange"
    />
    
    <!-- 新增/编辑对话框 -->
    <StudentForm
      v-model="dialogVisible"
      :student="currentStudent"
      @success="handleFormSuccess"
    />
  </div>
</template>

<style scoped>
.student-manager {
  padding: 20px;
}

.search-bar {
  margin-bottom: 16px;
  display: flex;
  gap: 8px;
}

.el-pagination {
  margin-top: 16px;
  justify-content: flex-end;
}
</style>

总结

本文通过学生管理案例,系统讲解了 Element Plus 的核心组件:

组件 作用 关键点
el-table 表格展示 自定义列用 #default="{ row }" 插槽
el-pagination 分页导航 后端分页配合 :total@current-change
el-dialog 对话框 双向绑定 v-model 控制显示
el-form 表单 校验规则 :rules,手动校验 validate()
ElMessage 消息提示 成功/警告/错误提示
ElMessageBox 确认框 删除等操作前确认

最佳实践

  1. 复杂表单抽取为子组件,通过 defineProps / defineEmits 通信
  2. 表单校验规则集中定义,类似 Java 的 validation 注解
  3. 分页由后端实现,前端只传递参数

下一步:掌握了单页面开发后,我们需要学习如何管理多个页面。下一篇文章将讲解 vue-router 路由管理。


作者:小张 | 转载请注明出处

相关推荐
爱勇宝1 小时前
《道德经》第 8 章:真正高级的能力,像水一样成事
前端·后端·程序员
Revolution611 小时前
数组本身没有 map,为什么还能直接调用:原型链怎样查找属性
前端·javascript·面试
bonechips1 小时前
RAG实战(一):EPUB 加载、文本切割与向量入库
前端·数据库
swipe1 小时前
10|(前端转全栈)库存扣减为什么最容易出事故?SKU、并发与原子更新
前端·后端·全栈
cdcdhj1 小时前
vue3中的watchEffect()监听,什么时候监听,什么时候清理,什么时候停止
前端·javascript·vue.js
huabuyu1 小时前
几百 MB 的文件为什么等几分钟才能打开?文件分片下载与渐进预览原理
前端·javascript
虚惊一场1 小时前
麻将桌上的并发控制:扫码进房、乐观版本与零和结算
前端·javascript
巴勒个啦1 小时前
从需求到上线:记录一次完全由 AI 辅助完成的小产品全流程
java·前端
无人生还2 小时前
从 Vue3 到 React · 快速上手系列第 6 篇:状态管理 useState 与 useReducer
前端·vue.js·react.js