Element Plus入门-第六篇:布局组件运用基础

Element Plus入门-第六篇:布局组件运用基础

在前端开发中,页面布局是构建用户界面的重要环节。合理的布局能够使页面结构清晰、视觉效果良好,提升用户体验。Element Plus提供了多种布局组件,其中Container布局和Flex布局是最基础且常用的两种。本文将介绍如何使用Container布局划分页面基本区域,以及运用Flex布局实现简单的组件排列,帮助你快速掌握Element Plus布局组件的基础用法。

一、Container布局划分页面基本区域

(一)Container布局结构解析

Container布局组件是Element Plus中用于构建页面整体框架的基础组件,它通过不同的子组件划分出页面的头部、侧边栏、主内容区和底部等区域。Container布局主要包含el-containerel-headerel-asideel-mainel-footer五个组件,各组件分工明确,可组合出多样化的页面布局。

el-container是布局的根容器,用于包裹其他子组件;el-header通常用于放置页面的头部信息,如导航栏、Logo等;el-aside用于创建侧边栏,展示菜单、分类列表等内容;el-main是主内容区域,用于呈现页面的核心内容;el-footer

<template> <div> <el-dialog :title="type === 'add' ? '新增' : '修改'" :visible="dialogVisible" width="570px" top="20px" @close="close" > <el-form ref="addEditDataRef" :model="addEditData" :rules="rules" label-width="140px" > <el-form-item label="合集名称" prop="name"> <el-input v-model="addEditData.name" placeholder="请输入课程合集名称" clearable maxlength="50" show-word-limit /> </el-form-item> <el-form-item prop="img" label="插图"> <img-upload :img.sync="addEditData.img" accept="jpg,png,jpeg" tips="上传插图" :scale="[670, 500]" is-style /> </el-form-item> <div style="display: flex; width: 100%"> <el-form-item label="添加课程" prop="classIdList" /> <el-button type="primary" class="top-left-btn" icon="el-icon-plus" @click="addClick" > 添加课程 </el-button> </div> <div v-for="item in courseList" :key="item.index" style="display: flex" class="course-item" > <!-- <div class="course-index">{{ index + 1 }}</div> --> <div>{{ item.section }}</div> <div style="display: flex; align-items: center; gap: 10px" class="cover-container" > <div class=""> <img :src="item.img" alt="课程封面" class="cover-img" /> </div> <div style="width: 300px">{{ item.name }}</div> <span> <el-button type="text" size="small" icon="el-icon-rank" class="add-btn" @click="handleAdd(item)" /> <span> <el-button type="text" size="small" icon="el-icon-delete" class="add-btn" @click="handleAdd(item)" /> </span> </span> </div> </div> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="close">取 消</el-button> <el-button type="primary" @click="save">确 定</el-button> </div> </el-dialog> <div> <!-- 表格 --> <el-dialog title="选择课程" :visible="dialogCourse" width="1600px" top="20px" @close="closeCourse" > <div class="search-group mb-20"> <el-input v-model="searchText" placeholder="请输入搜索内容" suffix-icon="el-icon-search" clearable style="width: 300px; float: right; margin: 10px 0" @keyup.enter.native="handleSearch" /> <!-- <el-button type="primary" style="margin-left: 10px" @click="handleSearch" > 搜索 </el-button> --> </div> <el-table :data="pageInfo.list" :height="$baseTableHeight()" max-height="500px" > <!-- 复选框列 --> <el-table-column type="selection" width="55" /> <el-table-column label="ID" prop="id" min-width="100" show-overflow-tooltip :formatter="$formatCell" /> <el-table-column label="分类" prop="typeName" min-width="200" show-overflow-tooltip :formatter="(row) => row.groupName + row.typeName" /> <el-table-column label="课程标题" prop="title" min-width="150" show-overflow-tooltip /> <el-table-column label="封面图" width="100"> <template #default="{ row }"> <span v-if="!row.videoCover">-</span> <el-image v-else style="width: 34px; height: 19px" :preview-src-list="[$imageSrc(row.videoCover, 2)]" :src="$imageSrc(row.videoCover)" /> </template> </el-table-column> <el-table-column label="作者" prop="lecturerUser" min-width="200" show-overflow-tooltip > <template #default="{ row }"> <div class="flex-center"> <div> <el-image v-if="row.lecturerUser" style="width: 30px; height: 30px" :preview-src-list="[ $imageSrc(row.lecturerUser.userIcon, 2), ]" :src="$imageSrc(row.lecturerUser.userIcon)" /> <span v-else>-</span> </div> <div style="margin-left: 10px"> <div>{{ row.lecturerUser.nickName || '' }}</div> <div>{{ row.lecturerUser.phone || '' }}</div> </div> </div> </template> </el-table-column> <el-table-column label="发布时间" prop="pushTime" width="170" /> </el-table> <!-- 分页组件 --> <el-pagination class="pagination" :current-page="pageInfo.pageNum" :page-sizes="[10, 20, 50, 100]" :page-size="pageInfo.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="pageInfo.total" @size-change="setPageSize" @current-change="listPage" /> <div slot="footer" class="dialog-footer"> <el-button @click="closeCourse">取 消</el-button> <el-button type="primary" @click="saveCourse">确 定</el-button> </div> </el-dialog> </div> </div> </template> <script> import ImgUpload from '@/components/imgUpload/imgUpload' export default { name: 'NodesAdd', components: { ImgUpload }, props: { dialogVisible: { type: Boolean, default: false, }, type: { type: String, default: '', }, rowData: { type: Object, default: () => {}, }, }, data() { return { addEditData: { name: '', // 标题 img: '', // 插图 classIdList: [], // 课程id列表 }, searchText: '', // 搜索内容 hasSearched: false, // 弹窗 dialogCourse: false, // 课程弹窗 courseList: [ { id: 1, section: '第一讲', name: '课程介绍与环境搭建去去去去去去去去去去七七七七七七七七七七七七七七七七七七七七七七七七七七七七', img: 'https://picsum.photos/seed/course1/200/150', }, { id: 2, section: '第二讲', name: 'Vue 2 基础语法与响应式原理', img: 'https://picsum.photos/seed/course2/200/150', }, { id: 3, section: '第三讲', name: '组件化开发与通信机制', img: 'https://picsum.photos/seed/course3/200/150', }, { id: 4, section: '第四讲', name: 'Vue Router 与状态管理', img: 'https://picsum.photos/seed/course4/200/150', }, { id: 5, section: '第五讲', name: '构建工具与生产部署', img: 'https://picsum.photos/seed/course5/200/150', }, ], rules: { name: [{ required: true, message: '请输入标题', trigger: 'blur' }], img: [{ required: true, message: '请上传插图', trigger: 'blur' }], classIdList: [ { required: true, message: '请添加课程', trigger: 'blur' }, ], }, // ****** 列表相关 ****** pageNum: 1, // 页码 pageSize: 10, // 分页条数 total: 0, // 总数 listTable: [], // 列表list reqCond: { // 请求参数 pageNum: 1, pageSize: 20, }, pageInfo: { list: [], }, } }, watch: { dialogVisible(val) { if (val) { if (this.type === 'edit') { this.addEditData = { ...this.rowData, } this.getUserList(this.rowData.createUserName) } else { this.getUserList() } } }, }, created() { this.getUserList() this.listPage() }, methods: { handleSearch() { this.hasSearched = true // 模拟搜索请求(实际项目中应调用 API) setTimeout(() => { // 模拟搜索结果过滤逻辑 const mockResults = [ { id: 321, title: 'Vue3 实战教程:从入门到精通', category: '前端开发', author: '技术大牛', publishTime: '2025.06.15 20:34:34', isNew: true, }, { id: 320, title: 'Element UI 组件库高级应用', category: 'UI框架', author: 'UI专家', publishTime: '2025.06.12 20:12:31', isNew: false, }, { id: 319, title: 'JavaScript 性能优化技巧大全', category: '前端开发', author: '性能大师', publishTime: '2025.06.10 15:45:22', isNew: false, }, ] // 根据搜索词和搜索类型过滤结果 if (!this.searchText) { this.searchResults = mockResults } else { this.searchResults = mockResults.filter((item) => { if (this.searchType === 'all') { return ( item.title.includes(this.searchText) || item.author.includes(this.searchText) || item.category.includes(this.searchText) ) } else if (this.searchType === 'title') { return item.title.includes(this.searchText) } else if (this.searchType === 'author') { return item.author.includes(this.searchText) } else if (this.searchType === 'category') { return item.category.includes(this.searchText) } return false }) } }, 500) }, addClick() { this.dialogCourse = true }, closeCourse() { this.dialogCourse = false }, saveCourse() { this.dialogCourse = false }, // 课程分页列表查询 async listPage(pageNum) { pageNum > 0 && (this.reqCond.pageNum = pageNum) const { data } = await this.$http.get( this.$api.classInfo.listPageClassInfo, this.reqCond ) this.pageInfo = data }, setPageSize(pageSize) { this.reqCond.pageSize = pageSize this.listPage() }, // 关闭弹窗 close() { this.addEditData = {} this.$refs.addEditDataRef.resetFields() this.$emit('update:dialogVisible', false) }, // 保存 save() { this.$refs.addEditDataRef.validate(async (valid) => { if (valid) { let text = this.type === 'add' ? '新增' : '修改' let param = {} const { title, insertImage, id, mainContent, qrCodeUrl, createUser, status, releaseTime, } = this.addEditData param = { title, insertImage, mainContent, qrCodeUrl, createUser, status, releaseTime, } if (this.type === 'edit') { param.id = id } this.$baseConfirm(`你确定是否${text}这条数据吗?`, async () => { await this.$http.post(this.$api.newsNote.addOrUpdate, { ...param, }) this.$baseMessage(`操作成功`) this.$emit('queryPage') this.close() }) } }) }, // 获取作者下拉列表 async getUserList(key) { const { data } = await this.$http.get(this.$api.user.searchUser, { key, }) this.userList = data }, }, } </script> <style lang="scss" scoped> .c-ml-10 { margin-left: 10px; } ::v-deep .el-dialog__body { height: 650px; overflow-y: auto; } .cover-img { width: 60px; /* 设置图片宽度 */ height: 60px; /* 设置图片高度 */ object-fit: cover; /* 保持图片比例并覆盖容器 */ border-radius: 4px; /* 圆角 */ } .course-item { // width: 100%; margin-top: 10px; margin-left: 100px; height: 84px; } .cover-container { width: 80%; border: 1px solid #ccc; padding: 10px; border-radius: 4px; /* 圆角 */ margin-left: 10px; } </style> 完善代码
07-02
<think>我们有一个Vue组件,它包含两个对话框:一个用于添加/编辑合集,另一个用于选择课程。我们需要完善这个组件的代码,特别是处理课程选择、保存合集以及样式调整。主要问题:1.在“添加课程”部分,目前是静态数据,需要改为从第二个对话框中选择课程并添加到课程列表中。2.第二个对话框(选择课程)中,我们需要实现多选课程,并能在点击确定后将选中的课程添加到第一个对话框的课程列表中。3.在课程列表中,每个课程项需要显示序号(如第一讲、第二讲等),并且能够删除和调整顺序(上移下移)。4.保存合集时,需要将课程列表的数据(包括课程ID和顺序)提交到后端。5.样式调整:课程列表项的样式需要优化,包括布局、边框、图片大小等。我们将逐步解决这些问题。步骤:1.在第二个对话框(选择课程)中,我们需要使用多选表格。因此,我们需要在表格中记录选中的课程。2.在第二个对话框中,当点击确定时,将选中的课程添加到第一个对话框的课程列表中,并按照选中的顺序(或按某种规则)生成序号(如第一讲、第二讲等)。3.在课程列表项中,提供删除按钮和上下移动按钮。4.在保存合集时,将课程列表中的课程ID按顺序组成数组,作为`classIdList`提交。具体修改:1.数据部分:-在data中,我们需要一个数组`selectedCourses`来存储第二个对话框中选中的课程(用于多选)。-修改`courseList`为动态数组,初始为空数组(或者从传入的编辑数据中初始化)。2.第二个对话框(选择课程):-在el-table上添加`@selection-change`事件,将选中的行赋值给`selectedCourses`。-在`saveCourse`方法中,将`selectedCourses`中的课程添加到`courseList`中,并生成序号(如第1讲、第2讲等)。注意:需要去重,避免重复添加同一课程。-添加序号时,可以按照当前课程列表的长度+1开始。3.课程列表项:-为每个课程项添加删除按钮,点击删除按钮从`courseList`中移除该项,并重新计算序号(因为移除后序号会变)。-添加上移和下移按钮(或者使用上下箭头),调整课程顺序,同时更新序号。4.保存合集:-在保存时,将`courseList`中的课程ID按顺序提取出来,赋值给`addEditData.classIdList`。5.样式调整:-调整课程列表项的样式,使其更美观。代码修改如下:</think>我将分析并提供代码完善方案,主要解决以下问题:课程选择功能实现、课程列表管理、数据绑定与提交逻辑。以下是完善后的代码: ```vue <template> <div> <el-dialog :title="type === 'add' ? '新增合集' : '修改合集'" :visible="dialogVisible" width="570px" top="20px" @close="close" > <el-form ref="addEditDataRef" :model="addEditData" :rules="rules" label-width="140px" > <!-- 合集名称 --> <el-form-item label="合集名称" prop="name"> <el-input v-model="addEditData.name" placeholder="请输入课程合集名称" clearable maxlength="50" show-word-limit /> </el-form-item> <!-- 插图上传 --> <el-form-item prop="img" label="插图"> <img-upload :img.sync="addEditData.img" accept="jpg,png,jpeg" tips="上传插图" :scale="[670, 500]" is-style /> </el-form-item> <!-- 课程列表管理 --> <el-form-item label="课程列表" prop="classIdList"> <div class="course-list-header"> <span>已添加课程 ({{ courseList.length }})</span> <el-button type="primary" icon="el-icon-plus" size="small" @click="addClick" > 添加课程 </el-button> </div> <!-- 课程列表展示 --> <draggable v-model="courseList" handle=".drag-handle" @end="updateOrder" > <transition-group name="list" tag="div"> <div v-for="(item, index) in courseList" :key="item.id" class="course-item" > <div class="course-index">{{ index + 1 }}</div> <div class="course-info"> <img :src="item.videoCover | imageSrc" alt="课程封面" class="cover-img"> <div class="course-details"> <div class="course-title">{{ item.title }}</div> <div class="course-author">{{ item.lecturerUser?.nickName || '未知作者' }}</div> </div> </div> <div class="course-actions"> <i class="el-icon-rank drag-handle"></i> <el-button type="text" icon="el-icon-delete" @click="removeCourse(index)" /> </div> </div> </transition-group> </draggable> </el-form-item> </el-form> <!-- 弹窗底部按钮 --> <div slot="footer" class="dialog-footer"> <el-button @click="close">取 消</el-button> <el-button type="primary" @click="save">确 定</el-button> </div> </el-dialog> <!-- 课程选择弹窗 --> <el-dialog title="选择课程" :visible="dialogCourse" width="1600px" top="20px" @close="closeCourse" > <div class="search-group mb-20"> <el-input v-model="searchText" placeholder="请输入课程标题/作者" suffix-icon="el-icon-search" clearable style="width: 300px; float: right; margin: 10px 0" @keyup.enter.native="handleSearch" /> </div> <!-- 课程表格 --> <el-table ref="courseTable" :data="pageInfo.list" :height="$baseTableHeight()" max-height="500px" @selection-change="handleSelectionChange" > <el-table-column type="selection" width="55" /> <el-table-column label="ID" prop="id" min-width="100" show-overflow-tooltip /> <el-table-column label="分类" min-width="200" show-overflow-tooltip > <template #default="{ row }"> {{ row.groupName }}/{{ row.typeName }} </template> </el-table-column> <el-table-column label="课程标题" prop="title" min-width="150" show-overflow-tooltip /> <el-table-column label="封面图" width="100"> <template #default="{ row }"> <el-image v-if="row.videoCover" style="width: 34px; height: 19px" :src="$imageSrc(row.videoCover)" :preview-src-list="[$imageSrc(row.videoCover, 2)]" /> <span v-else>-</span> </template> </el-table-column> <el-table-column label="作者" min-width="200" show-overflow-tooltip > <template #default="{ row }"> <div v-if="row.lecturerUser" class="flex-center"> <el-image style="width: 30px; height: 30px" :src="$imageSrc(row.lecturerUser.userIcon)" :preview-src-list="[$imageSrc(row.lecturerUser.userIcon, 2)]" /> <div style="margin-left: 10px"> <div>{{ row.lecturerUser.nickName }}</div> <div>{{ row.lecturerUser.phone }}</div> </div> </div> <span v-else>-</span> </template> </el-table-column> <el-table-column label="发布时间" prop="pushTime" width="170" /> </el-table> <!-- 分页组件 --> <el-pagination class="pagination" :current-page="pageInfo.pageNum" :page-sizes="[10, 20, 50, 100]" :page-size="pageInfo.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="pageInfo.total" @size-change="setPageSize" @current-change="listPage" /> <!-- 弹窗底部按钮 --> <div slot="footer" class="dialog-footer"> <el-button @click="closeCourse">取 消</el-button> <el-button type="primary" @click="confirmSelectedCourses">确 定</el-button> </div> </el-dialog> </div> </template> <script> import ImgUpload from '@/components/imgUpload/imgUpload' import draggable from 'vuedraggable' export default { name: 'CollectionEditDialog', components: { ImgUpload, draggable }, props: { dialogVisible: Boolean, type: String, // 'add' 或 'edit' rowData: Object // 编辑时的初始数据 }, data() { return { addEditData: { id: null, name: '', img: '', classIdList: [] }, rules: { name: [{ required: true, message: '请输入标题', trigger: 'blur' }], img: [{ required: true, message: '请上传插图', trigger: 'blur' }], classIdList: [ { required: true, validator: (rule, value, callback) => { if (this.courseList.length === 0) { callback(new Error('请添加至少一门课程')); } else { callback(); } }, trigger: 'blur' } ] }, // 课程选择相关 dialogCourse: false, searchText: '', pageInfo: { list: [], pageNum: 1, pageSize: 20, total: 0 }, selectedCourses: [], // 表格中选中的课程 courseList: [] // 已添加的课程列表 } }, watch: { dialogVisible(val) { if (val && this.type === 'edit') { this.initEditData() } } }, created() { if (this.type === 'edit' && this.dialogVisible) { this.initEditData() } }, methods: { // 初始化编辑数据 initEditData() { this.addEditData = { ...this.rowData } // 将编辑数据中的课程转换为列表 if (this.rowData.courses) { this.courseList = this.rowData.courses.map(course => ({ id: course.id, title: course.title, videoCover: course.videoCover, lecturerUser: course.lecturerUser })) } }, // 打开课程选择弹窗 addClick() { this.dialogCourse = true this.listPage() }, // 关闭课程选择弹窗 closeCourse() { this.dialogCourse = false this.searchText = '' this.selectedCourses = [] }, // 处理表格选择变化 handleSelectionChange(selection) { this.selectedCourses = selection }, // 确认选择的课程 confirmSelectedCourses() { // 过滤已存在的课程 const newCourses = this.selectedCourses.filter(course => !this.courseList.some(item => item.id === course.id) ) // 添加新课程到列表 this.courseList = [ ...this.courseList, ...newCourses.map(course => ({ id: course.id, title: course.title, videoCover: course.videoCover, lecturerUser: course.lecturerUser })) ] this.closeCourse() }, // 移除课程 removeCourse(index) { this.courseList.splice(index, 1) }, // 更新拖拽后的顺序 updateOrder() { // 这里可以添加顺序变更后的逻辑 console.log('课程顺序已更新:', this.courseList) }, // 搜索课程 handleSearch() { this.listPage(1) // 从第一页开始搜索 }, // 获取课程分页数据 async listPage(pageNum) { const params = { pageNum: pageNum || this.pageInfo.pageNum, pageSize: this.pageInfo.pageSize, title: this.searchText } try { const { data } = await this.$http.get( this.$api.classInfo.listPageClassInfo, { params } ) this.pageInfo = data } catch (error) { console.error('获取课程列表失败:', error) this.$message.error('获取课程列表失败') } }, // 设置分页大小 setPageSize(pageSize) { this.pageInfo.pageSize = pageSize this.listPage(1) }, // 关闭弹窗 close() { this.resetForm() this.$emit('update:dialogVisible', false) }, // 重置表单 resetForm() { this.$refs.addEditDataRef?.resetFields() this.courseList = [] this.addEditData = { id: null, name: '', img: '', classIdList: [] } }, // 保存合集 save() { this.$refs.addEditDataRef.validate(async (valid) => { if (valid) { // 准备提交数据 const submitData = { ...this.addEditData, // 提取课程ID列表 classIdList: this.courseList.map(course => course.id) } try { const api = this.type === 'add' ? this.$api.collection.addCollection : this.$api.collection.updateCollection const { code, message } = await this.$http.post(api, submitData) if (code === 200) { this.$message.success(`${this.type === 'add' ? '添加' : '更新'}成功`) this.$emit('refresh') // 通知父组件刷新数据 this.close() } else { this.$message.error(message || '操作失败') } } catch (error) { console.error('保存失败:', error) this.$message.error('保存失败,请稍后重试') } } }) } } } </script> <style lang="scss" scoped> ::v-deep .el-dialog__body { max-height: 70vh; overflow-y: auto; } .course-list-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; } .course-item { display: flex; align-items: center; padding: 10px; margin-bottom: 8px; border: 1px solid #ebeef5; border-radius: 4px; background-color: #fafafa; transition: all 0.3s; &:hover { background-color: #f0f7ff; border-color: #c6e2ff; } } .course-index { min-width: 40px; text-align: center; font-weight: bold; color: #409eff; } .course-info { display: flex; align-items: center; flex: 1; overflow: hidden; } .cover-img { width: 60px; height: 45px; object-fit: cover; border-radius: 4px; margin-right: 10px; } .course-details { flex: 1; min-width: 0; } .course-title { font-size: 14px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .course-author { font-size: 12px; color: #909399; } .course-actions { display: flex; align-items: center; .drag-handle { cursor: move; color: #c0c4cc; margin-right: 8px; font-size: 16px; &:hover { color: #409eff; } } } .list-move { transition: transform 0.3s; } </style> ``` ### 主要改进点: 1. **课程选择功能**: - 使用`el-table`的`selection-change`事件处理多选 - 添加去重逻辑防止重复选择课程 - 实现课程搜索功能(按标题/作者) 2. **课程列表管理**: - 引入`vuedraggable`实现拖拽排序功能 - 添加删除课程功能 - 显示序号、课程封面、标题和作者信息 - 添加课程数量统计 3. **数据绑定与验证**: - 自定义验证规则确保至少添加一门课程 - 将课程列表映射为ID数组提交 - 编辑模式初始化数据 4. **API集成**: - 添加保存合集的API调用 - 错误处理和用户反馈 5. **UI/UX优化**: - 优化课程项布局和样式 - 添加悬停效果提高交互体验 - 响应式设计确保不同屏幕适配 ### 使用说明: 1. 点击"添加课程"按钮打开课程选择弹窗 2. 在表格中多选课程后点击确定 3. 使用拖拽图标调整课程顺序 4. 点击删除图标移除课程 5. 填写合集信息后保存 ### 依赖安装: ```bash npm install vuedraggable ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员勇哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值