产出物
- Dialog对话框
技术项
- 组件插槽&具名插槽
- 组件通信 props 、emit、eventBus
注意
- 组件命名以两张方式二选一 :
- 1、首字母大写 ( 大驼峰 ) , 比如 Dialog
- 2、小写 用 短横线 - 连接 - my-dialog
/components/dialog
<template> <!--self:事件修饰符,只有点击自己才触发,点击子元素不触发 --> <div class="cat-dialog__wrapper" v-show="visible" @click.self="handleClose"> <!-- 对话框 --> <div class="cat-dialog" :style="{ width, marginTop: top }"> <!-- 对话框顶部 标题 + 关闭按钮 --> <div class="cat-dialog__header"> <slot name="title"> <span class="cat-dialog__title">{{ title }}</span> <button class="cat-dialog__headerbtn" @click="handleClose"> <i class="cat-icon-close"> x</i> </button> </slot> </div> <!-- 对话框内容 --> <div class="cat-dialog__body"> <slot></slot> </div> <!-- 对话框底部 一般都是一些操作,没有传入footer插槽就不显示v-if --> <div class="cat-dialog__footer" v-if="$slots.footer"> <slot name="footer"></slot> </div> </div> </div> </template> <script> export default { name: "gygDialog", props: { title: { type: String, default: "提示", }, // 弹框宽度 width: { type: String, default: "30%", }, // 弹框距离顶部距离 top: { type: String, default: "15vh", }, visible: { type: Boolean, default: false, }, }, methods: { handleClose() { //visible是父组件传过来的props,子组件不能直接修改,需要子传父 this.$emit("update:visible", false); }, }, }; </script> <style> .cat-dialog__wrapper{ position: fixed; left: 0; top: 0; width: 100vw; height: 100vh; background: gray; z-index: 100; display: flex; justify-content: center; align-content: center; } .cat-dialog { background: white; min-height: 30vh; max-height: 40vh; } .cat-dialog__header{ height: 20%; } .cat-dialog__body{ height: 60%; } .cat-dialog__footer{ height: 20%; } </style>
引入组件
Slot 插槽
Vue插槽(Slot)是一种非常有用的机制,可以让你更加灵活地组织和渲染组件。Vue中的插槽可分为三种类型:默认插槽、具名插槽和作用域插槽。默认插槽是没有指定名称的插槽;具名插槽为插槽指定名称,通常用于需要特定命名的内容的子组件中;作用域插槽允许子组件向父组件传递数据,并通常用于父组件需要以某种方式处理子组件数据的情况下。
使用场景举例:
默认插槽:当在组件中只需要将内容插入容器中,而不需要其他处理时,可以使用默认插槽。
具名插槽:当在组件中需要更复杂的布局时,可以使用具名插槽来分别处理不同的内容,比如一个博客组件可以分别使用“博客头部”、“博客正文”、“博客底部”三个具名插槽。
作用域插槽:当需要在父组件中操作子组件中的数据时,可以使用作用域插槽将子组件的数据传递给父组件处理,比如一个评论组件中需要实现评论的回复功能,可以使用作用域插槽将被回复评论的内容传递给父组件。
示例代码:
<!-- 默认插槽 --> <template> <div> <slot></slot> </div> </template> <!-- 具名插槽 --> <template> <div> <slot name="header"></slot> <slot name="content"></slot> <slot name="footer"></slot> </div> </template> <!-- 作用域插槽 --> <template> <div> <slot name="comment" v-for="(comment, index) in comments" :comment="comment" :index="index" :key="comment.id"> {{ comment.body }} </slot> </div> </template>
在这个示例中,我们分别使用了默认插槽、具名插槽和作用域插槽来渲染组件中的内容。通过具名插槽,我们可以分别控制组件中的头部、正文和底部;而使用作用域插槽,我们可以将评论组件中的数据传递给父组件进行操作。当然,在实际开发中,你可以根据需要使用不同类型的插槽来满足具体需求。
组件开发进阶
产出物
封装一个对话框组件
功能
- 自定义标题、内容、按钮
- 弹框打开、关闭
技术项
- 组件封装、引入
- 组件通信、props、eventBus(跨页面通信)
- slot (插槽)
- extend(创建复用组件)以API的形式调用组件
健壮性
- 样式统一
- props可扩展、校验字段类型
slot 插槽
如下弹框内容,需要考虑图片、文本、组件形式插入, props 只能满足单一数据类型,怎么办呢?
使用 slot 插槽 ,一种特殊组件,作用组件的占位符。
父页面使用插槽,可以放入任意内容(文本、图片、组件、作用域数据...)到组件内部
<template> <div class="dialog-box"> <!-- 弹框容器 --> <div id="dialog-body"> <!-- 标题 --> <div class="dialog-title"> {{ title }}</div> <!-- 弹框内容 --> <slot></slot> </div> </div> </template>
默认插槽
没有指定名称的 slot 插槽
父组件中没有指定插槽名称,内容会插入到组件内部中的 默认slot
比如:
Dialog 组件内部
<slot></slot>
父页面
<Dialog> <h1> 默认插槽进入 未指定 name 的slot 标签内 </h1> </Dialog>
具名插槽
当组件中需要复杂布局时,需要使用具名插槽,实现内容多样化
如:弹框的内容区域、底部按钮操作区域,使用具名插槽来分发内容
Dialog 组件中的内容和 页脚按钮,通过具名插槽插入内容
Dialog 组件内
<slot></slot> <div class="dialog-btn"> <!-- 底部按钮 具名插槽 --> <slot name="footer" userName="李四"> </slot> </div>
父页面
<Dialog> <template> 内容分发到 默认插槽中</template> <template v-slot:footer> 内容分发到 footer 具名插槽</template> </Dialog>
作用域插槽
允许父组件获取子组件作用域
在组件内部 为 slot
标签绑定属性,父组件中使用 v-slot
绑定作用域、接收参数
子组件
<!-- 组件内部绑定 userName 参数 --> <slot name="footer" userName="李四"> </slot>
父页面
v-slot:footer
绑定具名插槽v-slot:footer="scope"
获取组件内部作用域v-slot:footer="{userName}"
可通过解构获取参数
<template v-slot:footer="scope" > {{scope.userName}} <!-- 李四 --> </template >
extend() 构建组件
组件构造器,创建一个 vue 子类,可挂载指定dom元素上
优点:快速构建组件,实现复用
确定:代码可读性差,复杂组件会造成问题
常用全局类组件,如:弹框、提示 等交互组件
如下,在main.js 中 注册一个全局的弹框组件
// 引入dialog组件 import dialog from "@/components/DialogGyg.vue" // extend 组件构造器 const Dialog = Vue.extend(dialog) // 封装一个全局api,打开弹框 Vue.prototype.openAlert = (data)=>{ // 创建一个 vue 组件实例 let _alert = new Dialog() console.log('_alert',_alert) _alert.$data.show = true _alert.$slots.content =data.content _alert.$props.title = data.title _alert.$mount() // 组件挂载完成 // 将组件实例,插入到 页面中的body上 document.body.appendChild(_alert.$el) }
完整代码
app.vue 代码
<template> <div id="app"> <el-table :data="tableData" border style="width: 100%"> <el-table-column fixed prop="date" label="日期" width="150"> </el-table-column> <el-table-column prop="name" label="姓名" width="120"> </el-table-column> <el-table-column prop="province" label="省份" width="120"> </el-table-column> <el-table-column prop="city" label="市区" width="120"> </el-table-column> <el-table-column prop="address" label="地址" width="300"> </el-table-column> <el-table-column prop="zip" label="邮编" width="120"> </el-table-column> <el-table-column fixed="right" label="操作" width="100"> <template slot-scope="scope"> <el-button @click="tableRemove(scope.row)" type="text" size="small">删除</el-button> </template> </el-table-column> </el-table> <button @click="openalert"> 打开弹框</button> <DialogGyg ref="alert"> <div slot="content" v-if="activeData"> 是否删除 {{ activeData.name }} 数据? </div> <!-- <template slot="footer" > --> <template v-slot:footer="{age,slotName}" > <van-button type="primary" size="small" @click="closeBox('off')">取消{{ age }} </van-button> <van-button type="info" size="small" @click="closeBox('ok')">确定{{ slotName }}</van-button> </template > </DialogGyg> </div> </template> <script> import DialogGyg from './components/DialogGyg.vue'; export default { name: 'App', components: { DialogGyg }, data(){ return { // 当前编辑的表格数据 activeData:null, tableData: [{ date: '2016-05-02', name: '王小虎', id:0, province: '上海', city: '普陀区', address: '上海市普陀区金沙江路 1518 弄', zip: 200333 }, { date: '2016-05-04', name: '王小虎1', province: '上海', city: '普陀区', id:1, address: '上海市普陀区金沙江路 1517 弄', zip: 200333 }, { date: '2016-05-01', name: '王小虎2', province: '上海', city: '普陀区', id:2, address: '上海市普陀区金沙江路 1519 弄', zip: 200333 }, { date: '2016-05-03', name: '王小虎3', province: '上海', city: '普陀区', id:3, address: '上海市普陀区金沙江路 1516 弄', zip: 200333 }] // userName:"张三" } }, methods:{ openalert(){ // 通过$refs 操作组件内部的方法 // this.$refs.alert.openBox() this.openAlert({ title:"警告", content:"是否确认删除" }) }, tableRemove(data){ this.activeData = data; console.log('当前表格编辑数据') this.$refs.alert.openBox() }, closeBox(type){ this.$refs.alert.closeBox() if(type === 'ok'){ // 删除 当前选中的数据 // findIndex 找到当前选中数据,在数组中的索引位置 let Index = this.tableData.findIndex((item)=>item.id === this.activeData.id) console.log('Index',Index) this.tableData.splice(Index,1) } } } } </script> <style> #app { } </style>
Dialog 弹框组件
<template> <div class="dialog-box" v-if="show"> <!-- 弹框容器 --> <div class="dialog-body"> <!-- 标题 --> <div class="dialog-title"> {{ title }} <van-icon name="close" @click="closeBox" /> </div> <!-- 弹框内容 需要考虑图片、文本、组件形式插入到弹框内容中,如何实现呢? props只能满足单一数据类型,怎么办呢? 使用 slot 插槽 ,作用组件的占位符,父页面通过插槽往组件内部放入任意内容(文本、图片、组件...) --> <div class="dialog-content"> <slot name="content"> </slot> </div> <div class="dialog-btn"> <!-- 底部按钮 具名插槽 --> <slot name="footer" :slotName="name" age="18"> </slot> </div> </div> </div> </template> <script> export default { name: 'WebCodeDialogGyg', data() { return { name:"张三", show: false }; }, props: { title: { type: String, default: '弹框标题' }, }, mounted() { }, methods: { openBox() { this.show = true }, closeBox(){ this.show = false } }, }; </script> <style lang="scss" scoped> .dialog-box { z-index: 1000; width: 100vw; height: 100vh; background: rgba($color: #000000, $alpha: 0.7); display: flex; justify-content: center; align-items: center; position: fixed; left: 0; top:0; .dialog-content{ padding: 20px; } .dialog-body { width: 70vw; max-width: 300px; min-height: 100px; padding: 10px; background: white; } .dialog-title { display: flex; justify-content: space-between; } .dialog-btn{ width: 70%; margin: 0 auto; display: flex; justify-content: space-between; } } </style>
main.js 全局配置