制作 组件

Dialog

[toc]


Dialog需求分析与API设计

  • 借鉴 AntD/ Bulma/Eleme/iView/Vuetify 等
  • 需求
    • 点击后弹出
    • 有遮罩层
    • 有close按钮x
    • 有标题
    • 有内容
    • 有 OK/Cancel 按钮

API

1
2
3
4
5
<Dialog :visible="true"
        title="标题"
        @yes="fn1"
        @no="fn2"
></Dialog>

创建Dialog组件

Dialog.vue

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<script setup lang="ts">
import Button from '@/lib/Button.vue';
</script>

<template>
  <div class="vue-dialog-overlay"></div>
  <div class="vue-dialog-wrapper">
    <header>Title</header>
    <main>
      <p>some content...</p>
      <p>some content...</p>
    </main>
    <footer>
      <Button>OK</Button>
      <Button>Cancel</Button>
    </footer>
  </div>
</template>

<script lang="ts">
export default {
  name: 'Dialog'
};
</script>

DialogDemo.vue

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<script setup lang="ts">
import Dialog from '@/lib/Dialog.vue';
</script>

<template>
  <h1>Dialog 示例</h1>
  <h2>示例一</h2>
  <Dialog></Dialog>
</template>

<script lang="ts">
export default {
  name: 'DialogDemo'
};
</script>

Dialog.vue添加基本样式

  • 300ms点击穿透,在内容上加一层div.dialog

      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    
    <script setup lang="ts">
    import Button from '@/lib/Button.vue';
    </script>
    
    <template>
    <div class="vue-dialog-overlay"></div>
    <div class="vue-dialog-wrapper">
    <div class="vue-dialog">
      <header>Title <span class="vue-dialog-close"></span></header>
      <main>
        <p>some content...</p>
        <p>some content...</p>
      </main>
      <footer>
        <Button level="main">OK</Button>
        <Button>Cancel</Button>
      </footer>
    </div>
    </div>
    </template>
    
    <script lang="ts">
    export default {
    name: 'Dialog'
    };
    </script>
    
    <style lang="scss" scoped>
    @import 'var.scss';
    
    .vue-dialog {
    background: white;
    border-radius: $radius;
    box-shadow: 0 0 3px fade_out(black, 0.5);
    min-width: 15em;
    max-width: 90%;
    
    // 邻层的遮罩层
    &-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: fade_out(black, 0.5);
    z-index: 10;
    }
    
    // 外层样式
    &-wrapper {
    position: fixed;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    z-index: 11;
    }
    
    > header {
    padding: 12px 16px;
    border-bottom: 1px solid $border-color;
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-size: 20px;
    }
    
    > main {
    padding: 12px 16px;
    }
    
    > footer {
    border-top: 1px solid $border-color;
    padding: 12px 16px;
    text-align: right;
    }
    
    // 关闭的叉字
    &-close {
    position: relative;
    display: inline-block;
    width: 16px;
    height: 16px;
    cursor: pointer;
    
    &::before,
    &::after {
      content: '';
      position: absolute;
      height: 1px;
      background: black;
      width: 100%;
      top: 50%;
      left: 50%;
    }
    
    &::before {
      transform: translate(-50%, -50%) rotate(-45deg);
    }
    
    &::after {
      transform: translate(-50%, -50%) rotate(45deg);
    }
    }
    }
    </style>

让Dialog支持visible属性

勿用动词show表示是否可见 Dialog.vue

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
<template>
  <template v-show="visible">
    <div class="vue-dialog-overlay"></div>
    <div class="vue-dialog-wrapper">
      <div class="vue-dialog">
        <header>Title <span class="vue-dialog-close"></span></header>
        <main>
          <p>some content...</p>
          <p>some content...</p>
        </main>
        <footer>
          <Button level="main">OK</Button>
          <Button>Cancel</Button>
        </footer>
      </div>
    </div>
  </template>
</template>

<script setup lang="ts">
import Button from '@/lib/Button.vue';
import {onUpdated, toRefs} from 'vue';

const props = defineProps({
  visible: Boolean
});

// destructured prop visible is Value (integer for e.g.) which cannot be reactive by itself
// 解构出来的visible为简单类型,不再具有数据响应性
// 需要调用 toRefs(props) 赋予数据响应性
const {visible} = toRefs(props);

onUpdated(() => {
  console.log('visible: ', visible.value);
});

</script>

<style lang="scss" scoped>
@import 'var.scss';

.vue-dialog {
  background: white;
  border-radius: $radius;
  box-shadow: 0 0 3px fade_out(black, 0.5);
  min-width: 15em;
  max-width: 90%;

  // 邻层的遮罩层
  &-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: fade_out(black, 0.5);
    z-index: 10;
  }

  // 外层样式
  &-wrapper {
    position: fixed;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    z-index: 11;
  }

  > header {
    padding: 12px 16px;
    border-bottom: 1px solid $border-color;
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-size: 20px;
  }

  > main {
    padding: 12px 16px;
  }

  > footer {
    border-top: 1px solid $border-color;
    padding: 12px 16px;
    text-align: right;
  }

  // 关闭的叉字
  &-close {
    position: relative;
    display: inline-block;
    width: 16px;
    height: 16px;
    cursor: pointer;

    &::before,
    &::after {
      content: '';
      position: absolute;
      height: 1px;
      background: black;
      width: 100%;
      top: 50%;
      left: 50%;
    }

    &::before {
      transform: translate(-50%, -50%) rotate(-45deg);
    }

    &::after {
      transform: translate(-50%, -50%) rotate(45deg);
    }
  }
}
</style>

DialogDemo.vue

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<script setup lang="ts">
import {ref} from 'vue';
import Button from '@/lib/Button.vue';
import Dialog from '@/lib/Dialog.vue';

const x = ref(false);
const toggle = () => {
  x.value = !x.value;

  console.log(x.value);
};
</script>

<template>
  <h1>Dialog 示例</h1>
  <h2>示例一</h2>
  <Button @click="toggle">toggle</Button>
  <Dialog :visible="x"></Dialog>
  {{ x }}
</template>

<script lang="ts">
export default {
  name: 'DialogDemo'
};
</script>

解构props后,会使得数据失去响应,必须使用roRefs()重新赋予数据响应性

  • 或者直接使用props.xxx

使用<template v-show="visible">不显示