Vue: 造轮子-04:Dialog组件

746 阅读2分钟

Vue: 造轮子-04:Dialog组件

需求分析

  • 点击后弹出
  • 有遮罩层 overlay
  • 有 close 按钮
  • 有标题
  • 有内容
  • 有 yes / no 按钮
  1. 期望效果
<Dialog 
  visible
  title="标题"
  @yes="fn1" @no="fn2"
></Dialog>

支持visible属性

  1. 注意不要用show表示是否可见
  2. 创建dialog.vue和dialogDemo.VUE
  • dialog.vue overlay:笼罩层。wrapper:主体
<template>
    <div class="dialog-overlay"></div>
    <div class="dialog-wrapper">
        <header>标题</header>
        <main>
            <p>1</p>
            <p>2</p>
        </main>
        <footer>
            <Button>ok</Button>
            <Button>cancel</Button>
        </footer>
    </div>
</template>

dialog组件接受props:visible变量,用v-if判断是否显示。

让dialog可以点击关闭

  1. 注意不能通过修改 props.visible控制。不能直接改props
  2. 在dialog里添加close事件,用户点击触发。 dialog.VUE
 const close=()=>{
                context.emit('update:visible',false)
            }

dialogDemo.vue

<Dialog :visible="x" @update:visible = "x = $event"></Dialog>
// :visible= 和 @update:visible=可以合并为 v-model:visible=
<Dialog v-model:visible="x" ></Dialog>
  1. 如果想点击dialog黑色遮罩层时也关闭,只需要在遮罩层上也加上@click="close"即可。 但是这样体验不好(用户有时可能不小心就点到了黑色部分,就关闭了),所以可以新加一个props:closeOnClickOverlay

dialog-template

 <div class="gulu-dialog-overlay" @click="closeOnClickOverlay"></div>

dialog-script

 props:{
            visible:{
                type:Boolean,
                default: false
            },
            closeOnClickOverlay:{ // 是否点击遮罩层关闭,默认是
                type:Boolean,
                default:true
            },
          
        },
  setup(props,context){
    const close=()=>{
        context.emit('update:visible',false)
    }
    const closeOnClickOverlay=()=>{
        if(props.closeOnClickOverlay ){ // 如果开启了这个功能,才调用close,否则就什么都不做。
            close()
        }
    }
  
    return {close,closeOnClickOverlay}
}
  1. 点击cancel和ok
  • cancel触发cancel事件(f2),ok触发ok事件(f1)。
  • dialogDemo给dialog传f1和f2事件,dialog接受props,类型是Function
  • ok
 const ok=()=>{
              if(props.ok && props.ok()!==false){ //如果ok存在,且ok执行后的结果不是false.新写法: props.ok ?.()!==false
                  close()
              }
            }
  • cancel
 const cancel=()=>{
                context.emit('cancel')
                close()

            }
  1. 点击关闭代码
  • dialogDemo
<Dialog v-model:visible="x" :ok="f1" :cancel="f2"></Dialog>

export default {
        name: "DialogDemo",
        components:{Dialog,Button},
        setup(){
            const x =ref(false)
            const toggle = ()=>{
                console.log(x)
                x.value =!x.value
            }
            const f1=()=>{
                return true
            }
            const f2=()=>{
            }
            return {x,toggle,f1,f2}
        }
    }
  • dialog.vue
<template>
    <template v-if="visible">
    <div class="gulu-dialog-overlay" @click="closeOnClickOverlay"></div>
    <div class="gulu-dialog-wrapper">
        <div class="gulu-dialog">
        <header>标题
            <span @click="close" class="gulu-dialog-close"></span>
        </header>
        <main>
            <p>1</p>
            <p>2</p>
        </main>
        <footer>
            <Button @click="ok">ok</Button>
            <Button @click="cancel">cancel</Button>
        </footer>
        </div>
    </div>
    </template>

</template>

<script lang="ts">
    import Button from './button.vue'
    export default {
        name: "dialog",
        components:{Button},
        props:{
            visible:{
                type:Boolean,
                default: false
            },
            closeOnClickOverlay:{
                type:Boolean,
                default:true
            },
            ok:{
                type:Function
            },
            cancel:{
                type:Function
            }
        },
        setup(props,context){
            const close=()=>{
                context.emit('update:visible',false)
            }
            const closeOnClickOverlay=()=>{
                if(props.closeOnClickOverlay ){
                    close()
                }
            }
            const cancel=()=>{
                context.emit('cancel')
                close()

            }
            const ok=()=>{
              if(props.ok && props.ok()!==false){ //新写法: props.ok ?.()!==false
                  close()
              }

            }
            return {close,closeOnClickOverlay,cancel,ok}
        }
    }
</script>

支持title和content

  1. 使用插槽
  • dialogDemo
 <Dialog v-model:visible="x" :ok="f1" :cancel="f2">
        <template v-slot:content>
            <div>hi</div>
        </template>
        <template v-slot:title>
            <strong>加醋的title</strong>
        </template>

    </Dialog>
  • dialog
<template>
    <template v-if="visible">
    <div class="gulu-dialog-overlay" @click="closeOnClickOverlay"></div>
    <div class="gulu-dialog-wrapper">
        <div class="gulu-dialog">
        <header>
            <slot name="title"></slot>
            <span @click="close" class="gulu-dialog-close"></span>
        </header>
        <main>
            <slot name="content"></slot>
        </main>
        <footer>
            <Button @click="ok">ok</Button>
            <Button @click="cancel">cancel</Button>
        </footer>
        </div>
    </div>
    </template>

</template>
  • 语法:
// 外面
 <template v-slot:content></template>
 // 里面
  <main>
            <slot name="content"></slot>
  </main>

把dialog移动到body下面

  1. 为什么要放到body下面: 防止dialog被遮挡。
  2. 注意堆叠上下文
  3. 使用新组建:Teleport
  • 自带组件,不用引入
  • 作用: 把里面的东西放到body下面。
  • dialog.vue
<template>
    <template v-if="visible">
        <Teleport to="body">
            <div class="gulu-dialog-overlay" @click="closeOnClickOverlay"></div>
            <div class="gulu-dialog-wrapper">
                <div class="gulu-dialog">
                    <header>
                        <slot name="title"></slot>
                        <span @click="close" class="gulu-dialog-close"></span>
                    </header>
                    <main>
                        <slot name="content"></slot>
                    </main>
                    <footer>
                        <Button @click="ok">ok</Button>
                        <Button @click="cancel">cancel</Button>
                    </footer>
                </div>
            </div>
        </Teleport>

    </template>

</template>

一句话打开 Dialog

  1. 我不想声明 visible 变量,然后改变它的值
  2. 技术点:动态挂载组件
  3. 希望ooutput
<Button @click="showDialog">showDialog</Button>


const showDialog=()=>{
                openDialog({
                    title:'标题',
                    content:'HELLO',
                    ok(){
                        console.log('ok')
                    },
                    cancel(){
                        console.log('cancel')
                    }
                })
            }
  1. 新建openDialog.ts
import Dialog from './dialog.vue'
import {createApp,h} from 'vue'
export const openDialog = (options)=>{
    const {title, content,ok,cancel} = options
    const div = document.createElement('div')
    document.body.appendChild(div) //创建一个div,并且放到body里面。
    const close = ()=>{
        app.unmount(div)
        div.remove()
    }
    const app = createApp({
        render(){
            return h(Dialog,{visible:true, // 在渲染dialog的时候,传visible:true
                'onUpdate:visible':(newVisible)=>{ // 关闭,visible
                if(newVisible ===false){
                    close()
                  }
                },
                ok,cancel},{
                title:title,
                content:content
            })
        }
     })
     app.mount(div) // 把dialog放到div里面
}