Vue3 TelePort

Teleport Alert

父元件建立

  • 建立一個isOpenAlert=ref(true) =>放置到 return
  • openAlert與closeAlert函式 =>放置到 return
  • template 內 Teleport 下放置 to="#modal" `` **@closeFunction=> $emit('closeFunction')
  • 到App.vue放置 `` 對應 to="#modal"

父元件

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
<template>
<div>
<a @click="">打開Alert</a>
<!---template 內 放置 -->
<Teleport to="#modal">
<Alert v-if="isOpenAlert" @closeFunction="closeAlert" />
</Teleport>
</div>
</template>


<script>
import { ref } from 'vue'

// 引入子元件
import Alert from './Alert'

export default {
setup () {
const isOpenAlert = ref(false)
const openAlert = () => {
isOpenAlert.value = true
}
const closeAlert = () => {
isOpenAlert.value = false
}
return{
isOpenAlert,
openAlert,
closeAlert,
}
}
}
</script>

App.vue 這裡是跳出框的跳出位置 id與父元件的 #id對應

1
2
3
4
<template>
<div id="modal"></div>
</template>

子元件

  • 使用 @click="$emit('closeFunction')" 關閉必須與父元件的@closeFunction對應
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
<template>
<div
class="deposit-pop__wrap"
:class="tw([
'absolute',
'z-30',
'top-1/2',
'left-1/2',
'-translate-x-1/2',
'-translate-y-1/2',
'w-428px',
'max-w-full',
])"
>
<div
class="deposit-pop__container"
:class="tw([
'bg-depositBg',
'px-10px',
'pb-30px',
'mx-18px',
'md:mx-0',
'rounded-20px',
])"
>
<div
class="close__icon"
:class="tw([
'ml-auto',
'w-60px',
'h-60px',
'bg-white',
])"
:style="{
'-webkit-mask': setStyleMaskImage(require('./images/i_pad_menu_close.svg')),
'mask': setStyleMaskImage(require('./images/i_pad_menu_close.svg')),
}"
@click="$emit('closeFunction')"
>
</div>
<div>
Content
</div>
</div>
</div>
</template>


<script>
import { ref } from 'vue'
export default {
setup () {
const setStyleMaskImage = (url) => {
return `url(${url}) no-repeat center /contain`
}

return{
setStyleMaskImage,
}
}
}
</script>

參考資料
使用 Vue3 Teleport 製作對話框(Modal)
ithome Teleport
Teleport

第一種寫法

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
<template>

<button class="el-button el-button--primary"
@click="open = true">Open Modal
</button>

<!--Teleport 寫法-->
<Teleport to="body">
<div v-if="open" class="modal">
<div class="modal-main">
<div class="modal-header">
<a @click="open = false">
<i class="icon-cross"></i>
</a>
</div>
<div class="modal-body">
<p>Hello from the modal!</p>
</div>
</div>
</div>
</Teleport>
</template>

<script setup lang="ts">
import { ref} from 'vue';

const open = ref<boolean>(false);
const close = () => {
open.value=false
}
</script>

<style lang="scss">
.modal {
position: fixed;
z-index: 999;
top: 0%;
left: 0%;
width: 100vw;
margin-left: 0px;
height: 100vh;
background-color: #1610108a;
.modal-main{
width: 550px;
background-color: white;
margin: 100px auto;
border-radius: 8px;
padding: 25px;
.modal-header{
display: flex;
justify-content: flex-end;
i{
display: flex;
justify-content: flex-end;
}
}
}
}
@media (width<=767px) {
.modal {
.modal-main{
width: 90%;
max-width: 90%;
padding: 4%;
}
}
}
@media (width<=360px) {
.modal {
.modal-main{
width: 85%;
max-width: 85%;
}
}
}
</style>

父子間的傳值
父組件

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
117
118
119
120
121
122
123
<template>
<button class="el-button el-button--primary"
@click="open = true"
>Open Modal</button>

<Teleport to="body">
<Alert :open="open" :lists="lists" :dynamicValidateForm="dynamicValidateForm"
@sendOpen="close" :formRef="formRef"
@sendRemoveDomain="removeDomain" @sendSubmitForm="submitForm"
@sendResetForm="resetForm" @sendAddDomain="addDomain"/>
</Teleport>
</template>

<script setup lang="ts">
import { ref, reactive } from 'vue';
import Alert from './Alert.vue';

const open = ref<boolean>(false);

const close = () => {
open.value=false
}
const lists = ref<any[]>([
{ 'subject': 'one', id: 123 },
{ 'subject': 'two', id: 221 }
]);
import type { FormInstance } from 'element-plus'

const formRef = ref<FormInstance>()
const dynamicValidateForm = reactive<{
domains: DomainItem[]
email: string
}>({
domains: [
{
key: 1,
value: '',
},
],
email: '',
})

interface DomainItem {
key: number
value: string
}

const removeDomain = (item: DomainItem) => {
const index = dynamicValidateForm.domains.indexOf(item)
if (index !== -1) {
dynamicValidateForm.domains.splice(index, 1)
}
}

const addDomain = () => {
dynamicValidateForm.domains.push({
key: Date.now(),
value: '',
})
}

const submitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.validate((valid) => {
if (valid) {
console.log('submit!')
} else {
console.log('error submit!')
return false
}
})
}

const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
}
</script>

<style lang="scss">
.modal {
position: fixed;
z-index: 999;
top: 0%;
left: 0%;
width: 100vw;
margin-left: 0px;
height: 100vh;
background-color: #1610108a;
.modal-main{
width: 550px;
background-color: white;
margin: 100px auto;
border-radius: 8px;
padding: 25px;
.modal-header{
display: flex;
justify-content: flex-end;
i{
display: flex;
justify-content: flex-end;
}
}
}
}
@media (width<=767px) {
.modal {
.modal-main{
width: 90%;
max-width: 90%;
padding: 4%;
}
}
}
@media (width<=360px) {
.modal {
.modal-main{
width: 85%;
max-width: 85%;
}
}
}
</style>

Alert.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
<template>
<div v-if="open" class="modal">
<div class="modal-main">
<div class="modal-header">
<a @click="sendOpen"><i class="icon-cross"></i></a>
</div>
<div class="modal-body">
<el-form
ref="formRef"
:model="dynamicValidateForm"
label-width="120px"
class="demo-dynamic"
>
<el-form-item
prop="email"
label="Email"
:rules="[
{
required: true,
message: 'Please input email address',
trigger: 'blur',
},
{
type: 'email',
message: 'Please input correct email address',
trigger: ['blur', 'change'],
},
]"
>
<el-input v-model="dynamicValidateForm.email" />
</el-form-item>
<el-form-item
v-for="(domain, index) in dynamicValidateForm.domains"
:key="domain.key"
:label="'Domain' + index"
:prop="'domains.' + index + '.value'"
:rules="{
required: true,
message: 'domain can not be null',
trigger: 'blur',
}"
>
<el-input v-model="domain.value" />
<el-button class="mt-2" @click.prevent="sendRemoveDomain(domain)"
>Delete</el-button
>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="sendSubmitForm(formRef)">Submit</el-button>
<el-button @click="sendAddDomain">New domain</el-button>
<el-button @click="sendResetForm(formRef)">Reset</el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
</template>

<script setup lang="ts">
import { defineProps } from 'vue'

const props = defineProps({
open: { type: Boolean },
lists: { type: Object },
dynamicValidateForm: { type: Object },
formRef: { type: Object },
})

const emits = defineEmits(['sendOpen', 'sendRemoveDomain', 'sendSubmitForm','sendResetForm', 'sendAddDomain']);
const sendOpen = () => {
emits('sendOpen', props.open=false)
}
const sendRemoveDomain = (domain:any) => {
emits('sendRemoveDomain', domain)
}
const sendSubmitForm = (formRef: any) => {
emits('sendSubmitForm', formRef)
}
const sendResetForm = (formRef: any) => {
emits('sendResetForm', formRef)
}
const sendAddDomain = () => {
emits('sendAddDomain')
}
</script>

github Teleport說明