动态挂载组件
具体方法:
/*
* component: import( /* webpackPrefetch: true */,'路由'),
* // 组件
const com = (await component).default
// 方法一:
const CustomDialog= new (Vue.component('com', com))({
store,
router,
propsData: {
params
}
})
const DOM = document.createElement('div')
DOM.setAttribute('id', 'CustomDialog')
document.body.appendChild(DOM)
CustomDialog.$mount(DOM.appendChild(document.createElement('template')))
// 方法二:
const CustomDialog = new (Vue.extend(com))({
el: document.createElement('div'),
store,
router,
propsData: {
params
}
})
document.body.appendChild(CustomDialog.$el)
// 监听方法:
CustomDialog.$on('action', (res, data = null) => {})
*/
案例:
// 自定义弹出页面
Vue.prototype.$customDialog = async options => {
if (!window.customDialogObject) window.customDialogObject = {}
const customDialogId = parseInt(Math.random() * 10000000) + '_' + new Date().getTime()
const {component, params = {}, beforeClose = null} = options
// eslint-disable-next-line no-async-promise-executor
const com = (await component).default
const CustomDialog = new (Vue.extend(com))({
el: document.createElement('div'),
store,
router,
propsData: {
params,
// 还可以直接传入组件
// customComponent: com
// 然后在当前创建的组件里面直接使用 <component :is="customComponent"></component>
}
})
let actionObject = null
const Dom = document.createElement('div')
Dom.setAttribute('id', customDialogId)
if (position !== '') {
Dom.style = `position:fixed;width:100vw;height:100vh;top:0;left:0;display: flex;align-items: center;justify-content: center;z-index:${9999 +
Object.keys(window.customDialogObject).length * 2};`
const Dom2 = document.createElement('div')
Dom2.style = `position:absolute;width:100%;height:100%;top:0;left:0;z-index:1;background-color:rgba(0,0,0,0.7);`
Dom.appendChild(Dom2)
const Dom3 = document.createElement('div')
Dom3.style = `position:relative;z-index:10;transform: scale(0)`
Dom3.setAttribute('id', `${customDialogId}_el`)
Dom.appendChild(Dom3)
Dom3.appendChild(CustomDialog.$el)
} else {
Dom.appendChild(CustomDialog.$el)
}
document.body.appendChild(Dom)
return new Promise(resolve => {
setTimeout(() => {
if (document.getElementById(`${customDialogId}_el`)) {
document.getElementById(
`${customDialogId}_el`
).style = `position:relative;z-index:10;transform: scale(1);transition:all 0.3s;transform-origin: center center;`
}
const close = (returnData = null) => {
const fn = () => {
document.getElementById(customDialogId).parentNode.removeChild(document.getElementById(customDialogId))
resolve(returnData || actionObject)
}
if (position !== '' && !returnData) {
const styleString = document.getElementById(`${customDialogId}_el`).style
document.getElementById(
`${customDialogId}_el`
).style = `${styleString};transform: scale(0);transition:all 0.3s;transform-origin: center center;`
setTimeout(() => {
fn()
}, 300)
} else {
fn()
}
}
CustomDialog.$on('action', (res, data = null) => {
actionObject = {action: res, params: data, closeId: customDialogId}
if (beforeClose) {
beforeClose({...actionObject, close, options})
} else {
close()
resolve({...actionObject})
}
})
window.customDialogObject[customDialogId] = () => {
const returnData = {...actionObject, action: 'allClose'}
close(returnData)
}
}, 0)
})
}
//使用:
// 引用的路由需要:this.$emit('action', 'close', {a: 100})
this.$customDialog(
{
component:import('@/views/UsageGuide/components/Dialog/RedPacketDialog'),
params: {a: 100},
beforeClose: be => { // 关闭前拦截
be.close()
// Object.keys(window.customDialogObject).forEach(key => window.customDialogObject[key]()) // 关闭全部
}
}).then(res => {
console.log(res, '------------res-------------')
})
Elementui,通过Dialog,实现全局动态confirm自定义弹窗
// obj={
// class:'弹窗最外面的名称',
// title:'弹窗标题',
// components:'传入的组件,需要先使用 import test from './components/test.vue' 引入,然后传递 test(components和content只能有一个,并且components里面的关闭和取消按钮需要自己写)'
// propsData:组件的传递的参数(components传入的组件哈)
// content:'弹窗内容(可以文字、html,也可以展示代码需要`<div></div>`包裹)',
// width:'弹窗宽度',
// cancelText:'取消按钮的文案,没有传不显示按钮',
// confirmText:'确定按钮的文案,没有传不显示按钮',
// style:'content内容的基础样式',
// showClose:'是否显示弹窗右上角的关闭按钮',
// twiceTitle:'确定时的二次确认文字,不传不会二次确定'
// }
Vue.prototype.$confirmDialog = async function(obj = null) {
if (!obj) return
const idName = 'confirmDialog_new_custom_' + new Date().getTime() + '_' + parseInt(Math.random() * 1000)
const confirmDialog = (await import('@/components/dialog/confirmDialog.vue')).default
const ConfirmDialogCom = Vue.component('confirmDialog', confirmDialog)// 创建组件
// 给当前组件挂载父级页面的参数(即 main.js里面new Vue() 挂载的额外参数),这样内部的组件可以直接使用this.$router和this.$store,否则需要单独引入
const componentsArray = ['store', 'router']
const componentsObject = {
root: this.$root,
parent: this
}
componentsArray.forEach((item, index) => {
if (Object.keys(this.$root.$options).indexOf(item) != -1) {
componentsObject[item] = this[`$${item}`] || null
} else {
componentsArray[index] = null
}
})
// 这里主要是给confirmDialog.vue组件挂载'store', 'router','root','parent',这几个参数,并加载到组件上去,parent就是当前页面的实例
const confirmDialogComNew = new ConfirmDialogCom(componentsObject)// 创建组件实例(可以传递参数 { propsData: props })
// 将需要重新挂载的参数传到组件(这里传入components时,为了让confirmDialog.vue组件内部再动态添加组件时需要和上面一样挂载的)
confirmDialogComNew.componentsArray = [...['root', 'parent'], ...componentsArray].filter(n => n != null)
const DOM = document.createElement('div')
DOM.setAttribute('class', 'confirmDialog_new_custom')
DOM.setAttribute('id', idName)
document.body.appendChild(DOM)
const comp = confirmDialogComNew.$mount(DOM.appendChild(document.createElement('template')))// 将虚拟dom节点改变成真实dom,获取组件的dom节点,并实现挂载
comp.$watch('centerDialogVisible', (value) => { // 监听组件是否不显示,并删除组件、销毁组件
if (!value) {
document.body.removeChild(document.getElementById(idName))
comp.$destroy()
}
}, { deep: true })
return comp.showDialog(obj)
}
confirmDialog.vue组件内容
<template>
<div :class="[className]">
<el-dialog
:title="title"
:visible.sync="centerDialogVisible"
:width="width"
class="tishiDialog"
:modal-append-to-body="false"
:show-close="showClose"
:close-on-click-modal="closeOnClickModal"
:close-on-press-escape="closeOnPressEscape"
center
>
<template v-if="componentName">
<template v-if="propsData">
<div ref="componentCustom" />
</template>
<template v-else>
<component :is="componentName" ref="componentCustom" @close="close($event)"/>
</template>
</template>
<template v-else>
<div :style="contentStyle" :class="[$checkHtml(content)?'':'dialogContent']" v-html="content" />
</template>
<span slot="footer" class="dialog-footer">
<el-button v-if="cancelText!=''" class="close_btn" @click="closeFn(1)">{{ cancelText }}</el-button>
<el-button v-if="confirmText!=''" class="saves_btn" type="primary" @click="closeFn(2)">{{ confirmText }}</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
return {
dialogFn: null, // 回调方法
content: '', // 内容,可以是html
title: '', // 标题
width: '500px', // 宽度
centerDialogVisible: false,
cancelText: '', // 取消按钮文字,不传不显示取消按钮
confirmText: '', // 确定按钮文字,不传不显示确定按钮
contentStyle: { 'text-align': 'center' }, // 内容的基础样式
showClose: false, // 是否显示关闭按钮
btnClickType: 0,//点击按钮的判断,1:取消,2:确定,0:其他
closeOnClickModal: false, // 是否可以通过点击 modal 关闭 Dialog
closeOnPressEscape: false, // 是否可以通过按下 ESC 关闭 Dialog
twiceTitle: '', // 确定关闭时的二次确定文字,不传不会二次提示
className: ''//弹窗的close,
componentName: null,
propsData: null,//组件的参数
componentCustomDom: null
}
},
watch: {
centerDialogVisible(value) {
if (!value) {
if (this.componentCustomDom) {
this.componentCustomDom.$destroy()
}
if (this.btnClickType == 0) {
this.dialogFn({ close: true })
}
}
}
},
methods: {
// 判断是否存在html
checkHtml(htmlStr){
var reg = /<[^>]+>/g
return reg.test(htmlStr)
},
// 关闭按钮的操作,主要是二次确定需要的
hideFn(cancel, type) {
if (cancel) {
this.closed = true
this.centerDialogVisible = false
this.btnClickType = type
if (type == 1) {
this.dialogFn({ cancel: true })
} else if (type == 2) {
this.dialogFn({ confirm: true })
} else {
console.log('close')
}
}
},
// 关闭弹窗
closeFn(type) {
if (this.twiceTitle != '' && type == 2) {
this.$confirm(this.twiceTitle)
.then(_ => {
this.hideFn(true, type)
})
.catch(_ => {})
} else {
this.centerDialogVisible = false
this.btnClickType = type
if (type == 1) {
this.dialogFn({ cancel: true })
} else {
this.dialogFn({ confirm: true })
}
}
},
close(type = 'confirm') {
if (type == 'confirm') {
this.closeFn(2)
} else {
this.closeFn(1)
}
},
showDialog(obj) {
Object.assign(this.$data, this.$options.data.call(this))
if (obj.class) this.className = obj.class
if (obj.title) this.title = obj.title
if (obj.content) this.content = this.formatHtml(obj.content)
if (obj.width) this.width = obj.width
if (obj.cancelText) this.cancelText = obj.cancelText
if (obj.confirmText) this.confirmText = obj.confirmText
if (obj.style) this.contentStyle = obj.style
if (obj.showClose) this.showClose = obj.showClose
if (obj.twiceTitle) this.twiceTitle = obj.twiceTitle
if (obj.closeOnClickModal) this.closeOnClickModal = obj.closeOnClickModal
if (obj.closeOnPressEscape) this.closeOnPressEscape = obj.closeOnPressEscape
if (obj.propsData) this.propsData = obj.propsData
if (this.cancelText == '' && this.confirmText == '' && !obj.components) this.showClose = true
this.centerDialogVisible = true
this.dialogFn = obj.success || (() => {})
// 挂载组件
if (obj.components) {
if (obj.propsData) {
const ConfirmDialogCom = Vue.component('ConfirmSolt', obj.components)// 创建组件
const componentsObject = {}
// 这样内部的组件可以直接使用this.$router、this.$store、this.$parent及this.$root,否则需要单独引入(获取传入的componentsArray,然后再次挂载到ConfirmSolt组件上)
// ConfirmSolt组件的parent就是当前组件的this,其他的就直接区当前组件实例上的
this.componentsArray.forEach(item => {
componentsObject[item] = item == 'parent' ? this : this[`$${item}`]
})
// propsData 是组件传参
const confirmDialogComNew = new ConfirmDialogCom(Object.assign(componentsObject, { propsData: obj.propsData ? obj.propsData : {}}))// 创建组件实例(可以传递参数 { propsData: props })
confirmDialogComNew.$on('close', res => {
this.close(res)
})
this.componentName = 'ConfirmSolt'
this.$nextTick(() => {
const comp = confirmDialogComNew.$mount(this.$refs.componentCustom)
this.componentCustomDom = comp
})
} else {
Vue.component('ConfirmSolt', obj.components)
this.componentName = 'ConfirmSolt'
this.$nextTick(() => {
this.componentCustomDom = this.$refs.componentCustom
})
}
}
},
// 实现可以显示html标签
// this.content='`<div>测试显示div</div>`正常的文字'
formatHtml(val) {
const sing = '`'
const regxd = RegExp(`${sing}<[^${sing}]+>${sing}`, 'g')
val = val.replace(regxd, function(word) {
if (/<[^<]+>/g.test(val)) { // 判断是否存在html标签
const getHtml = (word) => {
let wordString = word.replace(/^(\s|`)+|(\s|`)+$/g, '')// 清除前后`符号
const htmlArray = []
wordString.replace(/<\/[^<]+>/g, function(word1) { // 获取每个标签类型的结束标签,即存在/的标签,比如:</div>
htmlArray.push(word1.replace(/^(\s|<\/)+|(\s|>)+$/g, ''))// 获取html里面存在的标签,并清除前<,后>
})
// 获取html标签以及中间的值
const htmlText = []
htmlArray.forEach(item => {
const regX = RegExp(`<${item}[^<]+<\/${item}>`, 'g')
console.log(regX)
wordString.replace(regX, function(word2) {
htmlText.push(word2)
})
})
console.log(htmlText)
htmlText.forEach(item => {
var ele = document.createElement('span')
ele.appendChild(document.createTextNode(item))
wordString = wordString.replace(RegExp(item, 'g'), `<span class='codeHtml' style='display: inline-block;padding: 4px 2px;background-color: #fff5f5;color: #ff502c;border-radius: 2px;'>${ele.innerHTML}</span>`)
})
return wordString
}
return getHtml(word)
} else {
return word
}
})
return val
}
}
}
</script>
<style lang="scss" scoped>
.dialogContent{
line-height: 2;
}
::v-deep .el-dialog{
margin-top:0 !important ;
top: 50%;
transform: translateY(-50%);
.el-dialog__header {
width: 100%;
height: 50px;
line-height: 50px;
position: relative;
background-color: #d90e19;
padding: 0;
.el-dialog__headerbtn{
top: 50%;
margin-top: -10px;
}
.el-dialog__title {
color: #fff;
font-size: 16px;
font-family: "PingFang";
font-weight: 500;
}
.el-dialog__close {
color: #fff;
}
}
.el-dialog__body {
padding-bottom: 10px;
}
.el-button{
margin-left: 0 !important;
margin-top: 10px;
}
.dialog-footer{
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
.close_btn {
width: 170px;
color: #d90e19;
border: 1px solid #d90e19;
background-color: #fff;
&:hover{
background-color: rgba(217, 14, 25,0.1);
border-color:rgba(217, 14, 25,0.1) ;
color: #d90e19;
}
}
.saves_btn {
width: 170px;
color: #fff;
background-color: #d90e19;
border-color:#d90e19 ;
margin-left: 20px;
&:hover{
background-color: #c0121b;
}
}
}
</style>
使用:
this.$confirmDialog({
title: '提示',
content: '《长恨歌》是唐代诗人白居易创作的一首长篇叙事诗。此诗可分为三大段,从“汉皇重色思倾国”至“惊破霓裳羽衣曲”共三十二句为第一段,写唐玄宗和杨贵妃的爱情生活、爱情效果,以及由此导致的荒政乱国和安史之乱的爆发。从“九重城阙烟尘生”至“魂魄不曾来入梦”共四十二句为第二段,写马嵬驿兵变,杨贵妃被杀,以及此后唐玄宗对杨贵妃朝思暮想,深情不移。从“临邛道士鸿都客”至“此恨绵绵无绝期”共四十六句为第三段,写唐玄宗派人上天入地到处寻找杨贵妃和杨贵妃在蓬莱宫会见唐玄宗使者的情形',
cancelText: '取消',
confirmText: '确定',
showClose:true,
success: (res) => {
console.log(res) //点击确定取消的返回值
}
})
1.如果传入显示的html,需要在弹窗传入一个class
this.$confirmDialog({
class: 'testDialog',//传入class
title: '提示',
content: '<div class="nameTitle">测试的文字</div>',
cancelText: '取消',
confirmText: '确定',
showClose:true,
success: (res) => {
console.log(res) //点击确定取消的返回值
}
})
最后在页面新增一个没有scoped的进行写样式,就可以实现自定义显示样式了
<style lang="scss">
.tishiDialog{
.nameTitle{
color:red
}
}
</style>
2. 弹窗需要显示html代码,需要使用``包裹
this.$confirmDialog({
title:'提示',
content:'`<div style="background: red;">红色背景的div</div>`首先,我们来说一下怎么实现背景色时红色的,比如这个div,要自己尝试哈!!!`<span>测试span标签的</span>`',
cancelText: '取消',
confirmText: '确定',
success: (res) => {
console.log(res)
}
})
3.如果components传入一个页面
async fn(){
this.$confirmDialog({
title: '测试的',
components: (await import('./components/test.vue')).default,
width: '400px',
success: (res) => {
console.log(res)
}
})
}
test.vue:
<template>
<div>
<div>测试文字的</div>
<span slot="footer" class="dialog-footer">
<el-button class="cancelBtn" @click="closeFn(1)">取消</el-button>
<el-button class="confirmBtn" type="primary" @click="closeFn(2)">确定</el-button>
</span>
</div>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
// 关闭
closeFn(type) {
if (type == 1) {
this.$parent.close('cancel')
//this.$emit('close','cancel')
} else {
// 这里时可以在组件里面调出二次确定的方法,主要使用的还是‘el-dialog’自带的‘$confirm’
// this.$parent.$confirm('确定同意?').then((res) => {
// if (res == 'confirm') {
// console.log('同意了哈')
// this.$parent.close('confirm')
// }
// }).catch((error) => {
// if (error == 'cancel') {
// console.log('没有同意哈')
// }
// })
this.$parent.close('confirm')
//this.$emit('close','confirm')
}
}
}
}
</script>
关闭弹窗需要使用 this.$parent.close( ) 方法
如果:
要使用 this.$parent.close,需要 给当前组件挂载父级页面的参数 ,上面代码有说明。
不然:
在内部页面使用router、store,需要单独 router,store使用需要单独引入,同时this.$parent无法使用,需要调用this.$emit('close','') 关闭
Elementui预览图片,无需单独引入组件:
// 预览大图
// url:展示的图片
// list:展示的所有图片数组
Vue.prototype.$lookImageViewer: async function(url, list) {
let listImg = list
const thisIndex = list.indexOf(url)
const firstArray = list.slice(thisIndex, list.length)
const twoArray = list.slice(0, thisIndex)
listImg = [...firstArray, ...twoArray]
// this.$viewerApi({ images: listImg })//v-viewer组件
const id = 'MyElImageViewer_' + new Date().getTime() + '_' + parseInt(Math.random() * 1000)
// 引用组件(找到Elementui中image-viewer的位置)
const ElImageViewer = (await import('element-ui/packages/image/src/image-viewer')).default
const MyElImageViewer = Vue.component('MyElImageViewer', ElImageViewer)
const MyElImageViewerNew = new MyElImageViewer({ propsData: {
urlList: listImg,
onClose: () => {
// 删除组件
compDOm.$destroy()
document.body.removeChild(document.getElementById(id))
}
}})
const DOM = document.createElement('div')
DOM.setAttribute('id', id)
DOM.setAttribute('class', 'imageSwipeViewer_Show')
document.body.appendChild(DOM)
// 挂载组件
const compDOm = MyElImageViewerNew.$mount(DOM.appendChild(document.createElement('template')))
compDOm.$nextTick(() => {
const showDom = document.getElementById(id)
showDom.querySelector('.el-icon-circle-close').style = 'font-size:38px;color:#fff'
})
}
使用:
this.$lookImageViewer('https://t7.baidu.com/it/u=1595072465,3644073269&fm=193&f=GIF',['https://t7.baidu.com/it/u=1595072465,3644073269&fm=193&f=GIF'])