Vue 3 与 Element Plus 组件开发实战案例解析
1. 基于 Element Plus 的动态表单校验实战
在 Vue 3 中结合 Element Plus 实现表单开发时,通常使用响应式对象来管理表单状态与校验规则。以下是一个订单信息录入的示例,展示了基础校验与重置逻辑。
<template>
<el-form :model="orderForm" :rules="validationRules" ref="formRef" label-width="120px">
<el-form-item label="项目名称" prop="title">
<el-input v-model="orderForm.title" placeholder="请输入项目名称" />
</el-form-item>
<el-form-item label="所属区域" prop="location">
<el-select v-model="orderForm.location" placeholder="请选择区域">
<el-option label="华东" value="east" />
<el-option label="华北" value="north" />
<el-option label="华南" value="south" />
</el-select>
</el-form-item>
<el-form-item label="截止日期" prop="deadline">
<el-date-picker v-model="orderForm.deadline" type="date" placeholder="选择日期" />
</el-form-item>
<el-form-item label="紧急程度" prop="priority">
<el-radio-group v-model="orderForm.priority">
<el-radio label="高" />
<el-radio label="普通" />
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSave(formRef)">确认保存</el-button>
<el-button @click="resetFields(formRef)">重置</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import { reactive, ref } from 'vue'
const formRef = ref()
const orderForm = reactive({
title: '',
location: '',
deadline: '',
priority: '普通'
})
const validationRules = {
title: [
{ required: true, message: '名称不能为空', trigger: 'blur' },
{ min: 5, max: 20, message: '字数需在5到20之间', trigger: 'blur' }
],
location: [{ required: true, message: '请选择区域', trigger: 'change' }]
}
const handleSave = async (el) => {
if (!el) return
await el.validate((isValid) => {
if (isValid) {
console.log('提交成功:', orderForm)
} else {
console.warn('验证未通过')
}
})
}
const resetFields = (el) => {
if (el) el.resetFields()
}
</script>
常见问题排查:必填项校验不消失
在使用 Element Plus 表单时,若遇到数据已填但校验提示不消失的情况,通常由以下原因导致:
- 字段未定义:在
reactive或ref定义的表单对象中,必须声明与prop对应的属性键。 - 触发方式不匹配:建议将
trigger设置为['blur', 'change']以确保在失去焦点和值变动时均能触发校验。
2. 幻灯片组件(Carousel)应用
轮播图是常见的 UI 组件。在 Vue 3 中,我们可以通过 v-for 动态渲染图片列表。
<template>
<div class="slider-container">
<el-carousel height="400px" indicator-position="outside">
<el-carousel-item v-for="(src, index) in bannerList" :key="index">
<img :src="src" class="banner-img" style="width:100%; height:100%" />
</el-carousel-item>
</el-carousel>
</div>
</template>
<script setup>
import { ref } from 'vue'
const bannerList = ref([
'https://via.placeholder.com/800x400/FF5733/FFFFFF',
'https://via.placeholder.com/800x400/33FF57/FFFFFF',
'https://via.placeholder.com/800x400/3357FF/FFFFFF'
])
</script>
3. Teleport 技术实现全屏模态框
Teleport 是 Vue 3 的核心特性,它允许我们将组件挂载到 DOM 树中 #app 之外的任意位置(如 body),非常适合处理弹窗覆盖问题。
<template>
<div class="container">
<el-button @click="isModalOpen = true">激活传送门弹窗</el-button>
<teleport to="body">
<div v-if="isModalOpen" class="overlay-mask">
<div class="modal-content">
<h3>系统提示</h3>
<p>此弹窗已传送至 body 根节点下。</p>
<el-button @click="isModalOpen = false">关闭</el-button>
</div>
</div>
</teleport>
</div>
</template>
<script setup>
import { ref } from 'vue'
const isModalOpen = ref(false)
</script>
<style scoped>
.overlay-mask {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.6);
display: flex; justify-content: center; align-items: center;
}
.modal-content {
background: #fff; padding: 30px; border-radius: 8px; text-align: center;
}
</style>
4. 任务列表(To-do List)简易应用
利用 Vue 3 的响应式 API,可以快速构建一个具有增删功能的任务清单。
<div id="todo-app">
<h2>{{ headerText }}</h2>
<div>
<input v-model="inputTask" placeholder="输入待办事项" />
<button @click="pushTask">添加</button>
</div>
<ul>
<li v-for="task in tasks" :key="task.id">
{{ task.content }}
<button @click="removeTask(task.id)">移除</button>
</li>
</ul>
</div>
<script>
const { createApp, ref, reactive } = Vue;
createApp({
setup() {
const headerText = ref('我的日程表');
const inputTask = ref('');
const tasks = reactive([
{ id: Date.now(), content: '早起跑步' }
]);
const pushTask = () => {
if (!inputTask.value) return;
tasks.push({ id: Date.now(), content: inputTask.value });
inputTask.value = '';
};
const removeTask = (id) => {
const idx = tasks.findIndex(t => t.id === id);
if (idx !== -1) tasks.splice(idx, 1);
};
return { headerText, inputTask, tasks, pushTask, removeTask };
}
}).mount('#todo-app');
</script>
5. 电影院选座逻辑实现
选座功能涉及复杂的数组状态管理。通过计算属性和 v-bind:class,可以实现座位状态的动态切换。
<script setup>
import { reactive, computed, toRefs } from 'vue'
const state = reactive({
selectedSeats: [],
rows: 10,
cols: 10,
// 0: 可选, 1: 已选, 2: 维修, -1: 空地
seatMap: [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 2, 2, 0, 0, 0, 0, 2, 2, 0,
0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
// ... 更多数据
],
price: 45
})
const totalPrice = computed(() => state.selectedSeats.length * state.price)
const toggleSeat = (index) => {
if (state.seatMap[index] === 0) {
if (state.selectedSeats.length >= 5) return alert('最多选5个座位')
state.seatMap[index] = 1
state.selectedSeats.push(index)
} else if (state.seatMap[index] === 1) {
state.seatMap[index] = 0
state.selectedSeats = state.selectedSeats.filter(i => i !== index)
}
}
</script>
6. 组件间的数据穿透:Provide/Inject
当层级较深时,使用 Provide 与 Inject 可以避免繁琐的 Prop 逐层传递。为了安全起见,通常配合 readonly 使用。
// 父组件 Parent.vue
<script setup>
import { provide, ref, readonly } from 'vue'
import ChildComponent from './Child.vue'
const configInfo = ref({ theme: 'dark', lang: 'zh-cn' })
provide('appConfig', readonly(configInfo))
</script>
// 深层子组件 Child.vue
<script setup>
import { inject } from 'vue'
const config = inject('appConfig', { theme: 'light' }) // 提供默认值
</script>
<template>
<div>当前主题: {{ config.theme }}</div>
</template>
7. 购物车全选与金额计算逻辑
在购物车场景中,computed 的 get 和 set 方法是实现"全选/反选"逻辑的最佳实践,同时使用 reduce 进行金额汇总。
<script setup>
import { reactive, computed } from 'vue'
const cartItems = reactive([
{ id: 1, name: '机械键盘', price: 299, count: 1, checked: true },
{ id: 2, name: '电竞鼠标', price: 159, count: 2, checked: false }
])
const isAllChecked = computed({
get: () => cartItems.every(item => item.checked),
set: (val) => cartItems.forEach(item => item.checked = val)
})
const totalAmount = computed(() => {
return cartItems
.filter(item => item.checked)
.reduce((sum, item) => sum + item.price * item.count, 0)
})
</script>
8. 数据表格的分页与行编辑
Element Plus 的 el-table 配合 el-pagination 可实现高效的数据展示。通过 v-model:current-page 实现双向绑定。
<template>
<el-table :data="pagedData" border>
<el-table-column prop="id" label="ID" width="100" />
<el-table-column prop="userName" label="用户名" />
<el-table-column label="操作">
<template #default="{ row }">
<el-button size="small" @click="editRow(row)">修改</el-button>
<el-button size="small" type="danger" @click="deleteRow(row)">注销</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
layout="prev, pager, next"
:total="allUsers.length"
:page-size="pageSize"
v-model:current-page="pageIndex"
/>
</template>
<script setup>
import { ref, computed } from 'vue'
const allUsers = ref([/* 大量用户数据 */])
const pageIndex = ref(1)
const pageSize = ref(10)
const pagedData = computed(() => {
const start = (pageIndex.value - 1) * pageSize.value
return allUsers.value.slice(start, start + pageSize.value)
})
</script>
9. 后台管理系统侧边菜单布局
侧边菜单通常由 el-menu 构成,配合 el-icon 增强视觉引导。可以使用嵌套循环处理二级或三级菜单。
<template>
<el-menu
class="sidebar-menu"
:collapse="collapsed"
background-color="#304156"
text-color="#bfcbd9"
>
<el-sub-menu v-for="menu in menuData" :key="menu.path" :index="menu.path">
<template #title>
<el-icon><component :is="menu.icon" /></el-icon>
<span>{{ menu.name }}</span>
</template>
<el-menu-item v-for="sub in menu.children" :key="sub.path" :index="sub.path">
{{ sub.name }}
</el-menu-item>
</el-sub-menu>
</el-menu>
</template>
<script setup>
import { ref } from 'vue'
const collapsed = ref(false)
const menuData = [
{ name: '用户管理', icon: 'User', path: '/users', children: [
{ name: '角色权限', path: '/roles' }
]},
{ name: '系统监控', icon: 'Odometer', path: '/monitor', children: [
{ name: '性能统计', path: '/stats' }
]}
]
</script>
