vue elementui 基础表格布局,基础表格+条件+搜索框布局,类型处理,父子组件方法调用 电脑版发表于:2020/6/6 10:20 [TOC] ### 基础表格+条件+搜索框布局 效果如下: ![](https://img.tnblog.net/arcimg/aojiancc2/25f8397bd9e64593812164c360a94bb5.png) 代码如下: ``` <template> <div class="app-container"> <el-card class="box-card"> <div slot="header" class="clearfix"> <div class="toolbar"> <div class="item-left"> <el-button type="primary" icon="el-icon-plus">添加</el-button> </div> <div class="item-right"> <div class="search"> <el-select v-model="pageParam.txid" placeholder="请选择课程体系" style="width: 140px;"> <el-option label="所有课程体系" value="" /> <el-option v-for="op in tixilist" :key="op.id" :label="op.name" :value="op.id" /> </el-select> <el-select v-model="pageParam.status" placeholder="请选择状态" style="width: 120px;"> <el-option label="所有状态" :value="-1" /> <el-option label="禁用" :value="0" /> <el-option label="启用" :value="1" /> </el-select> <el-select v-model="pageParam.type" placeholder="请选择类型" style="width: 120px;display: none;"> <el-option label="所有类型" :value="-1" /> <el-option label="评估" :value="0" /> <el-option label="实验" :value="1" /> </el-select> <el-input v-model="pageParam.key" placeholder="请输入内容" class="input-with-select" style="width: 240px;" /> <el-button slot="append" icon="el-icon-search" type="primary" @click="loadpagelist()">搜索 </el-button> </div> </div> </div> </div> <div> <el-table :data="courseList" stripe style="width: 100%" empty-text="暂无数据"> <el-table-column type="index" label="No" width="50" /> <el-table-column prop="courseName" label="课程名称" /> <el-table-column prop="tixi.name" label="课程体系" width="150" /> <el-table-column prop="courseType" label="课程类型" width="150"> <template slot-scope="scope"> <div> <span v-if="scope.row.courseType == 0">评估</span> <span v-else-if="scope.row.courseType == 1">实验</span> </div> </template> </el-table-column> <el-table-column prop="isEnable" label="状态" width="80"> <template slot-scope="scope"> <div> <span v-if="scope.row.isEnable">启用</span> <span v-else style="color: lightpink;">禁用</span> </div> </template> </el-table-column> <el-table-column prop="addManName" label="创建人" width="80" /> <el-table-column prop="addTime" label="创建时间" width="120" :formatter="(r, c, v) => { return $utils.colDateFormat(v, 'YYYY-MM-DD') }" /> <el-table-column prop="courseVersion" label="版本号" width="120" /> <el-table-column label="操作" width="180" fixed="right"> <div slot-scope="scope"> <el-button size="mini" type="text" @click="editcourse(scope.row)">编辑</el-button> <el-button size="mini" type="text" @click="editchapter(scope.row)">章节管理</el-button> <el-button v-if="type === 1" size="mini" type="text" @click="jumpLab(scope.row)">实验手册 </el-button> <el-button v-if="type === 0" size="mini" type="text" @click="jumpknow(scope.row)">知识点 </el-button> </div> </el-table-column> </el-table> <el-pagination background layout="prev, pager, next" :total="pageParam.dataCount" :page-size="pageParam.pageSize" :current-page="pageParam.pageIndex" @current-change="currentChange" /> </div> </el-card> </div> </template> <script> import Tinymce from '@/components/Tinymce' export default { name: 'Mycourseindex', components: { Tinymce }, props: { type: { // 类型 实验、评估 type: [Number], required: false, default: 0 } }, data() { return { editorToolBar: [ 'bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample' ], courseList: [], tixilist: [], pageParam: { pageIndex: 1, pageSize: 15, dataCount: 0, key: '', type: -1, status: -1, txid: '' }, } }, created() { }, mounted() { }, methods: { currentChange(curentIndex) { // const self = this // self.pageParam.pageIndex = curentIndex // self.loadpagelist() }, } } </script> <style lang="scss"> .myuploader { .el-upload { border: solid 1px #eee; border-radius: 6px; cursor: pointer; position: relative; overflow: hidden; } .el-upload:hover { border-color: #409EFF; } .avatar-uploader-icon { font-size: 28px; color: #8c939d; width: 80px; height: 80; line-height: 80px; text-align: center; } .avatar { width: 80px; height: 80px; display: block; } } </style> ``` ### 有逻辑代码,接口调用,弹窗查看详情,图片上传,图片预览,完善一些的 ![](https://img.tnblog.net/arcimg/aojiancc2/af7e584695f84388bfdc7e03ce8a579a.png) 页面: ``` <!-- 教师反馈 --> <template> <div class="app-container"> <el-card class="box-card"> <div slot="header" class="clearfix"> <div class="toolbar"> <div class="item-left"> <el-button type="primary" size="small" icon="el-icon-plus" @click="openFeedbackDialog()">反馈 </el-button> </div> <div class="item-right"> <div class="search"> <!-- <el-select v-model="pageParam.txid" placeholder="请选择课程体系" style="width: 140px;"> <el-option label="所有课程体系" value="" /> <el-option v-for="op in tixilist" :key="op.id" :label="op.name" :value="op.id" /> </el-select> <el-select v-model="pageParam.status" placeholder="请选择状态" style="width: 120px;"> <el-option label="所有状态" :value="-1" /> <el-option label="禁用" :value="0" /> <el-option label="启用" :value="1" /> </el-select> --> <!-- <el-select v-model="pageParam.type" placeholder="请选择类型" style="width: 120px;display: none;"> <el-option label="所有类型" :value="-1" /> <el-option label="评估" :value="0" /> <el-option label="实验" :value="1" /> </el-select> --> <!-- <el-select v-model="queryParms.feedbackType" size="small" class="query-condition" filterable @change="search"> <el-option label="反馈类型" value="" /> <el-option v-for="item in feedbackTypes" :key="item.feedbackType" :label="item.feedbackName" :value="item.feedbackType" /> </el-select> --> <el-select v-model="queryParms.dealType" size="small" class="query-condition" filterable @change="getData()" > <el-option label="处理类型" value="" /> <el-option v-for="item in dealTypes" :key="item.dealType" :label="item.dealTypeName" :value="item.dealType" /> </el-select> <el-select v-model="queryParms.feedbackType" size="small" class="query-condition" filterable @change="getData()" > <el-option label="反馈类型" value="" /> <el-option v-for="item in feedbackTypes" :key="item.feedbackType" :label="item.feedbackName" :value="item.feedbackType" /> </el-select> <el-input v-model="queryParms.QueryName" size="small" placeholder="请输入内容" class="input-with-select" style="width: 191px;" /> <el-button slot="append" size="small" icon="el-icon-search" type="primary" @click="getData()" >搜索 </el-button> </div> </div> </div> </div> <div> <el-table v-loading="loading" :data="dataList"> <el-table-column label="NO" type="index" width="50" fixed="left" /> <!-- <el-table-column label="姓名" prop="userShowName" width="80" /> <el-table-column label="账号" prop="userName" /> --> <el-table-column label="描述" prop="feedbackDescribe" /> <el-table-column prop="feedbackType" label="类型" width="100"> <template slot-scope="scope"> <el-tag v-if="scope.row.feedbackType == 1" type="danger">系统问题</el-tag> <el-tag v-if="scope.row.feedbackType == 2" type="warning">内容问题</el-tag> <el-tag v-if="scope.row.feedbackType == 3">我要吐槽</el-tag> </template> </el-table-column> <el-table-column prop="dealType" label="处理" width="100"> <template slot-scope="scope"> <el-tag v-if="scope.row.dealType == 0" type="warning">未处理</el-tag> <el-tag v-if="scope.row.dealType == 1" type="success">已处理</el-tag> <el-tag v-if="scope.row.dealType == 2" type="info">已驳回</el-tag> <el-tag v-if="scope.row.dealType == 3" type="warning">不予处理</el-tag> </template> </el-table-column> <!-- <el-table-column prop="dataFromType" label="来源" width="80"> <template slot-scope="scope"> <el-tag v-if="scope.row.dataFromType == 0" type="danger">未知</el-tag> <el-tag v-if="scope.row.dataFromType == 1" type="warning">实验</el-tag> <el-tag v-if="scope.row.dataFromType == 2" type="success">评估</el-tag> </template> </el-table-column> --> <!-- <el-table-column label="地址" prop="feedbackPath" width="100" show-overflow-tooltip /> --> <el-table-column label="反馈时间" width="115"> <template slot-scope="{ row }"> {{ $utils.colDateFormat(row.createTime, "YYYY-MM-DD") }} </template> </el-table-column> <el-table-column label="操作" width="70" :align="left" fixed="right"> <template slot-scope="{ row }" style="text-align:center"> <el-button size="mini" style="margin-left: -10px;" @click="showDetais(row)">详情</el-button> <!-- <el-link @click="showFeedbackDialog(row.id)">处理</el-link> --> <!-- <el-link style="margin-left:10px" @click="showDetais(row)">详情</el-link> --> </template> </el-table-column> </el-table> <el-pagination background layout="total,prev, pager, next" :total="queryParms.dataCount" :page-size="queryParms.PageSize" :current-page="queryParms.PageIndex" @current-change="currentChange" /> </div> </el-card> <el-dialog title="详情" :visible.sync="detailsDialogVisible" width="30%" :before-close="handleClose"> <div style="margin-top:9px"> <span style="font-weight:800"> 反馈内容:</span> <span>{{ details.feedbackDescribe }}</span> </div> <div style="margin-top:9px"> <span style="font-weight:800"> 处理状态:</span> <el-tag v-if="details.dealType == 0" type="warning">未处理</el-tag> <el-tag v-if="details.dealType == 1" type="success">已处理</el-tag> <el-tag v-if="details.dealType == 2" type="info">已驳回</el-tag> </div> <div v-if="details.dealingOpinion"> <div style="margin-top:9px"> <span style="font-weight:800"> 处理意见:</span> <span v-if="details.dealingOpinion">{{ details.dealingOpinion }}</span> </div> <div style="margin-top:9px"> <span style="font-weight:800"> 处理人员:</span> <span>{{ details.dealUserName }}</span> </div> <div style="margin-top:9px"> <span style="font-weight:800"> 处理时间:</span> <span>{{ details.dealTime }}</span> </div> </div> <div style="margin-top:9px;display:flex"> <div style="font-weight:800"> 问题截图:</div> <div style="display:flex"> <div v-for="(item, i) in details.feedbackImgs" :key="i + 'img'" style="margin-right:10px" class="img-waper" @click="onPreviewImg(i)" > <!-- 用id去显示图片,其实就是图片处理后的名称 --> <img style="cursor:pointer" :src="'/oss/api/ImageViewer/' + item.id + '.jpg?percent=0.6&quality=80&od=true'" width="70px" height="70px" > </div> </div> </div> <span slot="footer" class="dialog-footer"> <el-button @click="detailsDialogVisible = false">取 消</el-button> <el-button type="primary" @click="detailsDialogVisible = false">确 定</el-button> </span> </el-dialog> <!-- 封装的弹窗组件 --> <FeedbackDialog ref="feedbackDialog" /> <el-image-viewer v-if="showViewer" style="z-index:99999999" :on-close="closeViewer" :initial-index="initialIndex" :url-list="details.feedbackImgs.map(a => a.fileUrl)" /> </div> </template> <script> import FeedbackDialog from './components/feedbackDialog.vue' import ElImageViewer from 'element-ui/packages/image/src/image-viewer' export default { name: 'Mycourseindex', components: { FeedbackDialog, ElImageViewer }, props: { type: { // 类型 实验、评估 type: [Number], required: false, default: 0 } }, data() { return { editorToolBar: [ 'bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample' ], loading: false, detailsDialogVisible: false, dataList: [], // 反馈列表 tixilist: [], dealTypes: [ { dealType: '0', dealTypeName: '未处理' }, { dealType: '1', dealTypeName: '已处理' }, { dealType: '2', dealTypeName: '已驳回' } ], feedbackTypes: [ { feedbackType: '1', feedbackName: '系统问题' }, { feedbackType: '2', feedbackName: '内容问题' }, { feedbackType: '3', feedbackName: '我要吐槽' } ], queryParms: { pageIndex: 1, pageSize: 15, dataCount: 0, QueryName: '', key: '', type: -1, dealType: '', feedbackType: '', status: -1, txid: '' }, showViewer: false, details: { feedbackDescribe: '', dealingOpinion: '', dealTime: '', dealUserName: '', dealType: '', feedbackImgs: [] } } }, created() { }, mounted() { this.getData() }, methods: { getData() { this.loading = true this.$http .get( '/education/api/TeacherFeedback/GetFeedbackListByUser', this.queryParms ) .then((res) => { console.log(res) this.dataList = res.data.data this.loading = false this.queryParms.dataCount = res.data.dataCount }) }, // 打开图片预览 onPreviewImg(i) { // 设置预览图片的索引 this.initialIndex = i this.showViewer = true }, // 关闭图片预览 closeViewer() { this.showViewer = false }, currentChange(curentIndex) { // const self = this // self.pageParam.pageIndex = curentIndex // self.loadpagelist() }, showDetais(rowInp) { this.details.feedbackDescribe = rowInp.feedbackDescribe this.details.dealingOpinion = rowInp.dealingOpinion this.details.dealUserName = rowInp.dealUserName this.details.dealTime = rowInp.dealTime this.details.dealType = rowInp.dealType this.details.feedbackImgs = JSON.parse(rowInp.feedbackImgs) this.detailsDialogVisible = true }, openFeedbackDialog() { // 调用子组件的方法打开弹窗 this.$refs.feedbackDialog.openFeedbackDialog(this.dataFromType) } } } </script> <style lang="scss"> .myuploader { .el-upload { border: solid 1px #eee; border-radius: 6px; cursor: pointer; position: relative; overflow: hidden; } .el-upload:hover { border-color: #409EFF; } .avatar-uploader-icon { font-size: 28px; color: #8c939d; width: 80px; height: 80; line-height: 80px; text-align: center; } .avatar { width: 80px; height: 80px; display: block; } } </style> ``` 弹窗: ``` <!-- 老师进行进行反馈 --> <!-- 问题反馈使用的弹窗 --> <template> <div> <!-- 弹窗start --> <div class="dialogContent"> <el-dialog :visible.sync="dialogVisible" :show-close="false" width="700px" height="800px" :before-close="handleClose" > <div slot="title" class="dialog-footer"> <div style="font-size:16px"> 问题反馈 </div> <div class="separateline" /> </div> <div style="margin-top:-10px"> <div style="display:flex"> <div style="width:78px;color:#000"> 问题类型: </div> <div> <el-radio v-model="feedbackType" label="1">系统问题</el-radio> <el-radio v-model="feedbackType" label="2">内容问题</el-radio> <el-radio v-model="feedbackType" label="3">我要吐槽</el-radio> </div> </div> <div style="display:flex;margin-top: 30px;"> <div style="width:78px;color:#000;"> 描 述: </div> <div style="flex-grow: 1"> <el-input v-model="describe" type="textarea" style="width:100%" :rows="7" placeholder="请输入内容" /> </div> </div> <div style="display:flex;margin-top: 30px;"> <div style="width:78px;color:#000"> 问题截图: </div> <div style="flex-grow: 1"> <!-- 引用图片上传的组件 --> <UpLoadImg ref="upLoadImg" /> </div> </div> </div> <span slot="footer" class="dialog-footer"> <el-button size="small" @click="dialogVisible = false">我在想想</el-button> <el-button type="primary" size="small" @click="saveFeedback()">确定反馈</el-button> </span> </el-dialog> </div> <!-- 弹窗end --> </div> </template> <script> import UpLoadImg from './upLoadImg.vue' export default { // 组件名字 name: 'FeedbackDialog', components: { UpLoadImg }, // 组件参数 props: { index: { type: Number, default: 0 } }, data() { return { feedbackType: '3', describe: '', dataFromType: 0, dialogVisible: false } }, mounted() { // alert("组件内部加载好了"+this.dialogVisibleParameter) this.initPic() }, // 组件方法 methods: { initPic() { }, // 模态框关闭前的事件 handleClose(done) { }, // 打开弹窗 openFeedbackDialog(_dataFromType) { this.dialogVisible = true this.dataFromType = _dataFromType }, // 清空一下反馈的数据,比如反馈成功之后清空一下,这样下次打开的时候不会让前面的数据还在 clearFeedbackData() { this.feedbackType = '3' this.describe = '' // 清空上传的图片(通过调用子组件的方法实现,因为图片这块数据在子组件) this.$refs.upLoadImg.clearImgs() }, // 确定反馈 saveFeedback() { const _this = this if (!_this.describe) { this.$alert('请输入反馈内容') return } this.dialogVisible = false const classID = this.$route.query.classid // 从上传图片的子组件获取(调用子组件的方法获取) const imgJson = this.$refs.upLoadImg.getImgs() // 反馈回传的数据 const feedbackData = { FeedbackImgs: JSON.stringify(imgJson), // 上传的图片 FeedbackType: _this.feedbackType, // 反馈类型 FeedbackDescribe: _this.describe, // 反馈的描述 ClassId: classID, // 班级id DataFromType: _this.dataFromType, // 数据来源1:实验 2:评估 FeedbackPath: window.location.href // 反馈的地址。就是学生反馈时所在的页面 } console.log(feedbackData) this.$http.post('/education/api/TeacherFeedback/SaveFeedback', feedbackData).then(res => { this.$alert('反馈成功') _this.clearFeedbackData() // 调用父组件的刷新方法 _this.$parent.getData() console.log(res) }).catch(e => { } ) } } } </script> <style scoped="scoped" lang="scss"> // 自定义的一根简单分割线 .separateline { height: 1px; background: #eee; margin-left: -40px; margin-right: -40px; margin-top: 9px; } // 问题截图,传递图片的样式 .form-content.panle { // padding: 20px; padding-bottom: 10px; // background-color: #f8f8f8; .img-boxs { display: flex; /* margin-right: 46px; */ flex-wrap: wrap; .img-waper { margin-right: 10px; margin-bottom: 10px; position: relative; .remove-icon:hover { cursor: pointer; img { opacity: 0.6; } } .remove-icon { position: absolute; width: 14px; height: 14px; top: -8px; right: -5px; img { width: 100%; } } } } .add-btn:hover { cursor: pointer; opacity: 0.6; } .add-btn { width: 70px; height: 70px; border: solid 1px #DDDDDD; display: flex; align-items: center; flex-wrap: wrap; justify-content: space-around; .icon-waper { width: 18px; height: 21px; position: relative; .icon-x { width: 19px; height: 5px; background-color: #DDDDDD; position: absolute; top: 0; bottom: 0; margin: auto; } .icon-y { height: 21px; width: 5px; background-color: #DDDDDD; position: absolute; left: 0; right: 0; margin: auto; } } } } </style> <!-- 修改elementui里边对话框的默认样式 需要注意两点: 1:样式声明的时候不要加scoped,不然样式只在当前组件起作用,无法覆盖样式内容的样式 2:要修改默认弹窗里边的样式,加一个前缀,就可以做到只修改当前这个组件的了,不然可能会影响到其他地方的样式 --> <style lang="scss"> /* .dialogContent .el-dialog { background-color: #F6F8FC; } .dialogContent .el-dialog__header { padding: 20px 40px 10px 40px } .dialogContent .el-dialog__body { padding: 30px 40px; color: #606266; font-size: 14px; word-break: break-all; } */ // 修改el-dialog里边的默认样式 .dialogContent { .el-dialog { background-color: #F6F8FC; } .el-dialog__header { padding: 20px 40px 10px 40px; background-color: #f6f8fc !important; } .el-dialog__body { padding: 30px 40px; color: #606266; font-size: 14px; word-break: break-all; } .el-dialog__footer { padding: 0px 40px 30px 40px } // 修改默认按钮的padding也就是跳转按钮的宽度 .el-button--small, .el-button--small.is-round { padding: 9px 16px; } // 修改默认按钮的圆角为直角 .el-button--mini, .el-button--small { border-radius: 0px; } } </style> ``` 图片上传的组件 ``` <!-- 问题反馈使用的弹窗 --> <template> <div> <!-- 图片上传start --> <div class="form-content panle"> <div class="img-boxs"> <div v-for="(item,i) in taskResoult.imgJson" :key="i+'img'" class="img-waper" @click="onPreviewImg(i)"> <img :src="'/oss/api/ImageViewer/'+item.id+'.jpg?percent=0.6&quality=80&od=true'" width="79px" height="79px" > <div class="remove-icon" @click="rmImgs(i)"> <img src="/asserts/img/error.png"> </div> </div> <div v-if="taskResoult.imgJson.length<9" class="img-waper"> <div class="add-btn" @click="addImgClick"> <input ref="inputImgFile" type="file" style="display: none;" accept="image/*" @change="uploadimg" > <div class="icon-waper"> <div class="icon-x" /> <div class="icon-y" /> </div> </div> </div> </div> </div> <!-- 图片上传end --> <!-- 图片预览start --> <el-image-viewer v-if="showViewer" :z-index="20000" :initial-index="initialIndex" :append-to-body="true" :on-close="closeViewer" :mask-closable="false" :url-list="taskResoult.imgJson.map(a=>a.fileUrl)" /> <!-- 图片预览end --> </div> </template> <script> import ElImageViewer from 'element-ui/packages/image/src/image-viewer' export default { // 组件名字 name: 'UpLoadImg', components: { ElImageViewer }, // 组件参数 props: { index: { type: Number, default: 0 } }, data() { return { showViewer: false, initialIndex: 0, taskResoult: { 'uploadFileID': '', stuImgs: '', imgJson: [], 'uploadFileInfo': '', uploadFileInfoJson: {}, 'remarks': null } } }, mounted() { this.initPic() }, // 组件方法 methods: { initPic() { }, // 点击添加图片,直接触发文件选择框的事件即可 addImgClick() { this.$refs.inputImgFile.click() }, // 打开图片预览 onPreviewImg(i) { // 设置预览图片的索引 this.initialIndex = i this.showViewer = true }, // 获取当前上传的图片 getImgs() { return this.taskResoult.imgJson }, // 清空当前选择的图片 clearImgs() { this.taskResoult.imgJson = [] this.$forceUpdate() }, // 关闭图片预览 closeViewer() { this.showViewer = false }, // 点击叉叉删除图片 rmImgs(i) { this.taskResoult.imgJson.splice(i, 1) // console.log(self.taskResoult) this.$forceUpdate() }, // 上传图片逻辑。文件选择框的change事件触发 uploadimg(e) { // 日精进上传图片 const imgFileExtensionArry = ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp', 'psd', 'svg', 'tiff'] const file = e.target.files[0] const fileExt = file.name.substr(file.name.lastIndexOf('.') + 1) if (imgFileExtensionArry.indexOf(fileExt.toLowerCase()) === -1) { this.$message.warning('仅允许提交图片文件!') return } const formData = new FormData() formData.append('bucketName', 'labroom') formData.append('filePath', 'feedback') formData.append('FileType', 1) formData.append('file', file) if (this.taskResoult.stuImgs && this.taskResoult.imgJson.length === 9) { this.$message.error('不允许超过9张图片') return } const self = this this.$http.post('/oss/api/SmartFiles/UpLoadFormFile', formData).then(res => { const imgInfo = { id: res.data.id, fileName: res.data.title, fileUrl: '/oss/api/ImageViewer/' + res.data.id + '.jpg' } self.taskResoult.imgJson.push(imgInfo) console.log(self.taskResoult) self.$forceUpdate() }) } } } </script> <style scoped="scoped" lang="scss"> // 问题截图,传递图片的样式 .form-content.panle { // padding: 20px; padding-bottom: 10px; // background-color: #f8f8f8; .img-boxs { display: flex; /* margin-right: 46px; */ flex-wrap: wrap; .img-waper { margin-right: 10px; margin-bottom: 10px; position: relative; .remove-icon:hover { cursor: pointer; img { opacity: 0.6; } } .remove-icon { position: absolute; width: 14px; height: 14px; top: -8px; right: -5px; img { width: 100%; } } } } .add-btn:hover { cursor: pointer; opacity: 0.6; } .add-btn { width: 79px; height: 79px; border: solid 1px #DDDDDD; display: flex; align-items: center; flex-wrap: wrap; justify-content: space-around; .icon-waper { width: 18px; height: 21px; position: relative; .icon-x { width: 19px; height: 5px; background-color: #DDDDDD; position: absolute; top: 0; bottom: 0; margin: auto; } .icon-y { height: 21px; width: 5px; background-color: #DDDDDD; position: absolute; left: 0; right: 0; margin: auto; } } } } </style> ```