Vue3.0仿layer全局对话框组件|vue3自定义弹层
介绍
v3layer弹层 基于vue3.0开发的仿layer.js桌面端对话框组件。支持7+弹窗动画、10+弹窗类型、30+参数配置,拥有流畅的拖拽、缩放、最大化及全屏等功能。轻松实现各种弹窗效果。
vue3.0 mobile移动端自定义弹层组件v3popup
V3Layer支持 Msg、Modal、Dialog、Message、Notification、ActionSheet、Toast、Popover 等多种弹窗类型。
引入组件
// 在main.js中全局引入组件
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// 引入饿了么vue3组件库
import ElementPlus from 'element-plus'
import 'element-plus/lib/theme-chalk/index.css'
// 引入弹窗组件
import Vue3Layer from './components/v3layer'
app.use(ElementPlus)
app.use(Vue3Layer)
app.mount('#app')
支持如下两种调用形式。
标签式
<v3-layer
v-model="showDialog"
title="标题内容"
content="<div style='color:#f57b16;padding:30px;'>这里是内容信息!</div>"
z-index="1011"
lockScroll="false"
xclose
resize
dragOut
:btns="[
{text: '取消', click: () => showDialog=false},
{text: '确认', style: 'color:#f90;', click: handleSure},
]"
>
<template v-slot:content>这里是自定义插槽内容信息!</template>
</v3-layer>
函数式
let $el = v3layer({
title: '标题内容',
content: '<div style='color:#f57b16;padding:30px;'>这里是内容信息!</div>',
shadeClose: false,
zIndex: 1011,
lockScroll: false,
xclose: true,
resize: true,
dragOut: true,
btns: [
{text: '取消', click: () => { $el.close() }},
{text: '确认', click: () => handleSure},
]
});
如上图:还支持 message popover nofity 三种弹窗类型。
vue2中可以通过prototype来实现挂载全局方法。
vue3中提供了两种全新的挂载全局方法 app.config.globalProperties 和 app.provide 。
如果使用第一种方式:
// vue2.x中调用
methods: {
showDialog() {
this.$v3layer({...})
}
}
// vue3.x中调用
setup() {
// 获取上下文
const { ctx } = getCurrentInstance()
ctx.$v3layer({...})
}
如果使用第二种方式:
// vue2.x中调用
methods: {
showDialog() {
this.v3layer({...})
}
}
// vue3.x中调用
setup() {
const v3layer = inject('v3layer')
const showDialog = () => {
v3layer({...})
}
return {
v3layer,
showDialog
}
}
大家感兴趣可以去官网看看文档说明。
https://v3.cn.vuejs.org/api/application-config.html#globalproperties
https://v3.cn.vuejs.org/guide/component-provide-inject.html
编码实现
v3layer支持如下参数自定义配置。
|props参数|
v-model 是否显示弹框
id 弹窗唯一标识
title 标题
content 内容(支持String、带标签内容、自定义插槽内容)***如果content内容比较复杂,推荐使用标签式写法
type 弹框类型(toast|footer|actionsheet|actionsheetPicker|android|ios|contextmenu|drawer|iframe)
layerStyle 自定义弹窗样式
icon toast图标(loading | success | fail)
shade 是否显示遮罩层
shadeClose 是否点击遮罩时关闭弹窗
lockScroll 是否弹窗出现时将body滚动锁定
opacity 遮罩层透明度
xclose 是否显示关闭图标
xposition 关闭图标位置(left | right | top | bottom)
xcolor 关闭图标颜色
anim 弹窗动画(scaleIn | fadeIn | footer | fadeInUp | fadeInDown | fadeInLeft | fadeInRight)
position 弹出位置(auto | ['100px','50px'] | t | r | b | l | lt | rt | lb | rb)
drawer 抽屉弹窗(top | right | bottom | left)
follow 跟随元素定位弹窗(支持元素.kk #kk 或 [e.clientX, e.clientY])
time 弹窗自动关闭秒数(1、2、3)
zIndex 弹窗层叠(默认8080)
teleport 指定挂载节点(默认是挂载组件标签位置,可通过teleport自定义挂载位置) teleport="body | #xxx | .xxx"
topmost 置顶当前窗口(默认false)
area 弹窗宽高(默认auto)设置宽度area: '300px' 设置高度area:['', '200px'] 设置宽高area:['350px', '150px']
maxWidth 弹窗最大宽度(只有当area:'auto'时,maxWidth的设定才有效)
maximize 是否显示最大化按钮(默认false)
fullscreen 全屏弹窗(默认false)
fixed 弹窗是否固定
drag 拖拽元素(可定义选择器drag:'.xxx' | 禁止拖拽drag:false)
dragOut 是否允许拖拽到窗口外(默认false)
lockAxis 限制拖拽方向可选: v 垂直、h 水平,默认不限制
resize 是否允许拉伸尺寸(默认false)
btns 弹窗按钮(参数:text|style|disabled|click)
++++++++++++++++++++++++++++++++++++++++++++++
|emit事件触发|
success 层弹出后回调(@success="xxx")
end 层销毁后回调(@end="xxx")
++++++++++++++++++++++++++++++++++++++++++++++
|event事件|
onSuccess 层打开回调事件
onEnd 层关闭回调事件
v3layer组件模板
<template>
<div ref="elRef" v-show="opened" class="vui__layer" :class="{'vui__layer-closed': closeCls}" :id="id">
<!-- //蒙版 -->
<div v-if="JSON.parse(shade)" class="vlayer__overlay" @click="shadeClicked" :style="{opacity}"></div>
<div class="vlayer__wrap" :class="[''+anim, type&&'popui__'+type, tipArrow]" :style="[layerStyle]">
<div v-if="title" class="vlayer__wrap-tit" v-html="title"></div>
<div v-if="type=='toast'&&icon" class="vlayer__toast-icon" :class="['vlayer'+icon]" v-html="toastIcon[icon]"></div>
<div class="vlayer__wrap-cntbox">
<!-- 判断插槽是否存在 -->
<template v-if="$slots.content">
<div class="vlayer__wrap-cnt"><slot name="content" /></div>
</template>
<template v-else>
<template v-if="content">
<iframe v-if="type=='iframe'" scrolling="auto" allowtransparency="true" frameborder="0" :src="content"></iframe>
<!-- message|notify|popover -->
<div v-else-if="type=='message' || type=='notify' || type=='popover'" class="vlayer__wrap-cnt">
<i v-if="icon" class="vlayer-msg__icon" :class="icon" v-html="messageIcon[icon]"></i>
<div class="vlayer-msg__group"><div v-if="title" class="vlayer-msg__title" v-html="title"></div><div v-html="content"></div></div>
</div>
<div v-else class="vlayer__wrap-cnt" v-html="content"></div>
</template>
</template>
<slot />
</div>
<div v-if="btns" class="vlayer__wrap-btns">
<span v-for="(btn,index) in btns" :key="index" class="btn" :style="btn.style" @click="btnClicked($event,index)" v-html="btn.text"></span>
</div>
<span v-if="xclose" class="vlayer__xclose" :class="!maximize&&xposition" :style="{'color': xcolor}" @click="close"></span>
<span v-if="maximize" class="vlayer__maximize" @click="maximizeClicked($event)"></span>
<span v-if="resize" class="vlayer__resize"></span>
</div>
<!-- 优化拖拽卡顿 -->
<div class="vlayer__dragfix"></div>
</div>
</template>
v3layer逻辑处理
<script>
import { onMounted, onUnmounted, ref, reactive, watch, toRefs, nextTick } from 'vue'
import domUtils from './utils/dom.js'
// 索引,蒙层控制,定时器
let $index = 0, $locknum = 0, $timer = {}, $closeTimer = null
export default {
props: {
// ...
},
emits: [
'update:modelValue'
],
setup(props, context) {
const elRef = ref(null);
const data = reactive({
opened: false,
closeCls: '',
toastIcon: {
// ...
},
messageIcon: {
// ...
},
vlayerOpts: {},
tipArrow: null,
})
onMounted(() => {
if(props.modelValue) {
open();
}
window.addEventListener('resize', autopos, false);
})
onUnmounted(() => {
window.removeEventListener('resize', autopos, false);
clearTimeout($closeTimer);
})
// 监听弹层v-model
watch(() => props.modelValue, (val) => {
// console.log('V3Layer is now [%s]', val ? 'show' : 'hide')
if(val) {
open();
}else {
close();
}
})
// 打开弹窗
const open = () => {
if(data.opened) return;
data.opened = true;
typeof props.onSuccess === 'function' && props.onSuccess();
const dom = elRef.value;
// 弹层挂载位置
if(props.teleport) {
nextTick(() => {
let teleportNode = document.querySelector(props.teleport);
teleportNode.appendChild(dom);
auto();
})
}
callback();
}
// 关闭弹窗
const close = () => {
if(!data.opened) return;
let dom = elRef.value;
let vlayero = dom.querySelector('.vlayer__wrap');
let ocnt = dom.querySelector('.vlayer__wrap-cntbox');
let omax = dom.querySelector('.vlayer__maximize');
data.closeCls = true;
clearTimeout($closeTimer);
$closeTimer = setTimeout(() => {
data.opened = false;
data.closeCls = false;
if(data.vlayerOpts.lockScroll) {
$locknum--;
if(!$locknum) {
document.body.style.paddingRight = '';
document.body.classList.remove('vui__body-hidden');
}
}
if(props.time) {
$index--;
}
// 清除弹窗样式
vlayero.style.width = vlayero.style.height = vlayero.style.top = vlayero.style.left = '';
ocnt.style.height = '';
omax && omax.classList.contains('maximized') && omax.classList.remove('maximized');
data.vlayerOpts.isBodyOverflow && (document.body.style.overflow = '');
context.emit('update:modelValue', false);
typeof props.onEnd === 'function' && props.onEnd();
}, 200)
}
// 弹窗位置
const auto = () => {
// ...
autopos();
// 全屏弹窗
if(props.fullscreen) {
full();
}
// 弹窗拖动|缩放
move();
}
const autopos = () => {
if(!data.opened) return;
let oL, oT
let pos = props.position;
let isFixed = JSON.parse(props.fixed);
let dom = elRef.value;
let vlayero = dom.querySelector('.vlayer__wrap');
if(!isFixed || props.follow) {
vlayero.style.position = 'absolute';
}
let area = [domUtils.client('width'), domUtils.client('height'), vlayero.offsetWidth, vlayero.offsetHeight]
oL = (area[0] - area[2]) / 2;
oT = (area[1] - area[3]) / 2;
if(props.follow) {
offset();
}else {
typeof pos === 'object' ? (
oL = parseFloat(pos[0]) || 0, oT = parseFloat(pos[1]) || 0
) : (
pos == 't' ? oT = 0 :
pos == 'r' ? oL = area[0] - area[2] :
pos == 'b' ? oT = area[1] - area[3] :
pos == 'l' ? oL = 0 :
pos == 'lt' ? (oL = 0, oT = 0) :
pos == 'rt' ? (oL = area[0] - area[2], oT = 0) :
pos == 'lb' ? (oL = 0, oT = area[1] - area[3]) :
pos == 'rb' ? (oL = area[0] - area[2], oT = area[1] - area[3]) :
null
)
vlayero.style.left = parseFloat(isFixed ? oL : domUtils.scroll('left') + oL) + 'px';
vlayero.style.top = parseFloat(isFixed ? oT : domUtils.scroll('top') + oT) + 'px';
}
}
// 元素跟随定位
const offset = () => {
let oW, oH, pS
let dom = elRef.value
let vlayero = dom.querySelector('.vlayer__wrap');
oW = vlayero.offsetWidth;
oH = vlayero.offsetHeight;
pS = domUtils.getFollowRect(props.follow, oW, oH);
data.tipArrow = pS[2];
vlayero.style.left = pS[0] + 'px';
vlayero.style.top = pS[1] + 'px';
}
// 最大化弹窗
const full = () => {
// ...
}
// 恢复弹窗
const restore = () => {
let dom = elRef.value;
let vlayero = dom.querySelector('.vlayer__wrap');
let otit = dom.querySelector('.vlayer__wrap-tit');
let ocnt = dom.querySelector('.vlayer__wrap-cntbox');
let obtn = dom.querySelector('.vlayer__wrap-btns');
let omax = dom.querySelector('.vlayer__maximize');
let t = otit ? otit.offsetHeight : 0
let b = obtn ? obtn.offsetHeight : 0
if(!data.vlayerOpts.lockScroll) {
data.vlayerOpts.isBodyOverflow = false;
document.body.style.overflow = '';
}
props.maximize && omax.classList.remove('maximized')
vlayero.style.left = parseFloat(data.vlayerOpts.rect[0]) + 'px';
vlayero.style.top = parseFloat(data.vlayerOpts.rect[1]) + 'px';
vlayero.style.width = parseFloat(data.vlayerOpts.rect[2]) + 'px';
vlayero.style.height = parseFloat(data.vlayerOpts.rect[3]) + 'px';
}
// 拖动|缩放弹窗
const move = () => {
// ...
}
// 事件处理
const callback = () => {
// 倒计时关闭
if(props.time) {
$index++
// 防止重复点击
if($timer[$index] !== null) clearTimeout($timer[$index])
$timer[$index] = setTimeout(() => {
close();
}, parseInt(props.time) * 1000)
}
}
// 点击最大化按钮
const maximizeClicked = (e) => {
let o = e.target
if(o.classList.contains('maximized')) {
// 恢复
restore();
} else {
// 最大化
full();
}
}
// 点击遮罩层
const shadeClicked = () => {
if(JSON.parse(props.shadeClose)) {
close();
}
}
// 按钮事件
const btnClicked = (e, index) => {
let btn = props.btns[index]
if(!btn.disabled) {
typeof btn.click === 'function' && btn.click(e)
}
}
return {
...toRefs(data),
elRef,
close,
maximizeClicked,
shadeClicked,
btnClicked,
}
}
}
</script>
大家可以在此逻辑基础上自行开发一些新功能。
okay,基于vue3开发自定义pc端弹窗组件就分享这么多,希望以上分享对大家有所帮助哈~~
React+Hooks自定义pc端弹窗组件|react.js自定义对话框
链接:https://juejin.cn/post/6913710656428457991/
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
介绍
v3layer弹层 基于vue3.0开发的仿layer.js桌面端对话框组件。支持7+弹窗动画、10+弹窗类型、30+参数配置,拥有流畅的拖拽、缩放、最大化及全屏等功能。轻松实现各种弹窗效果。
vue3.0 mobile移动端自定义弹层组件v3popup
V3Layer支持 Msg、Modal、Dialog、Message、Notification、ActionSheet、Toast、Popover 等多种弹窗类型。
引入组件
// 在main.js中全局引入组件
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// 引入饿了么vue3组件库
import ElementPlus from 'element-plus'
import 'element-plus/lib/theme-chalk/index.css'
// 引入弹窗组件
import Vue3Layer from './components/v3layer'
app.use(ElementPlus)
app.use(Vue3Layer)
app.mount('#app')
支持如下两种调用形式。
标签式
<v3-layer
v-model="showDialog"
title="标题内容"
content="<div style='color:#f57b16;padding:30px;'>这里是内容信息!</div>"
z-index="1011"
lockScroll="false"
xclose
resize
dragOut
:btns="[
{text: '取消', click: () => showDialog=false},
{text: '确认', style: 'color:#f90;', click: handleSure},
]"
>
<template v-slot:content>这里是自定义插槽内容信息!</template>
</v3-layer>
函数式
let $el = v3layer({
title: '标题内容',
content: '<div style='color:#f57b16;padding:30px;'>这里是内容信息!</div>',
shadeClose: false,
zIndex: 1011,
lockScroll: false,
xclose: true,
resize: true,
dragOut: true,
btns: [
{text: '取消', click: () => { $el.close() }},
{text: '确认', click: () => handleSure},
]
});
如上图:还支持 message popover nofity 三种弹窗类型。
vue2中可以通过prototype来实现挂载全局方法。
vue3中提供了两种全新的挂载全局方法 app.config.globalProperties 和 app.provide 。
如果使用第一种方式:
// vue2.x中调用
methods: {
showDialog() {
this.$v3layer({...})
}
}
// vue3.x中调用
setup() {
// 获取上下文
const { ctx } = getCurrentInstance()
ctx.$v3layer({...})
}
如果使用第二种方式:
// vue2.x中调用
methods: {
showDialog() {
this.v3layer({...})
}
}
// vue3.x中调用
setup() {
const v3layer = inject('v3layer')
const showDialog = () => {
v3layer({...})
}
return {
v3layer,
showDialog
}
}
大家感兴趣可以去官网看看文档说明。
https://v3.cn.vuejs.org/api/application-config.html#globalproperties
https://v3.cn.vuejs.org/guide/component-provide-inject.html
编码实现
v3layer支持如下参数自定义配置。
|props参数|
v-model 是否显示弹框
id 弹窗唯一标识
title 标题
content 内容(支持String、带标签内容、自定义插槽内容)***如果content内容比较复杂,推荐使用标签式写法
type 弹框类型(toast|footer|actionsheet|actionsheetPicker|android|ios|contextmenu|drawer|iframe)
layerStyle 自定义弹窗样式
icon toast图标(loading | success | fail)
shade 是否显示遮罩层
shadeClose 是否点击遮罩时关闭弹窗
lockScroll 是否弹窗出现时将body滚动锁定
opacity 遮罩层透明度
xclose 是否显示关闭图标
xposition 关闭图标位置(left | right | top | bottom)
xcolor 关闭图标颜色
anim 弹窗动画(scaleIn | fadeIn | footer | fadeInUp | fadeInDown | fadeInLeft | fadeInRight)
position 弹出位置(auto | ['100px','50px'] | t | r | b | l | lt | rt | lb | rb)
drawer 抽屉弹窗(top | right | bottom | left)
follow 跟随元素定位弹窗(支持元素.kk #kk 或 [e.clientX, e.clientY])
time 弹窗自动关闭秒数(1、2、3)
zIndex 弹窗层叠(默认8080)
teleport 指定挂载节点(默认是挂载组件标签位置,可通过teleport自定义挂载位置) teleport="body | #xxx | .xxx"
topmost 置顶当前窗口(默认false)
area 弹窗宽高(默认auto)设置宽度area: '300px' 设置高度area:['', '200px'] 设置宽高area:['350px', '150px']
maxWidth 弹窗最大宽度(只有当area:'auto'时,maxWidth的设定才有效)
maximize 是否显示最大化按钮(默认false)
fullscreen 全屏弹窗(默认false)
fixed 弹窗是否固定
drag 拖拽元素(可定义选择器drag:'.xxx' | 禁止拖拽drag:false)
dragOut 是否允许拖拽到窗口外(默认false)
lockAxis 限制拖拽方向可选: v 垂直、h 水平,默认不限制
resize 是否允许拉伸尺寸(默认false)
btns 弹窗按钮(参数:text|style|disabled|click)
++++++++++++++++++++++++++++++++++++++++++++++
|emit事件触发|
success 层弹出后回调(@success="xxx")
end 层销毁后回调(@end="xxx")
++++++++++++++++++++++++++++++++++++++++++++++
|event事件|
onSuccess 层打开回调事件
onEnd 层关闭回调事件
v3layer组件模板
<template>
<div ref="elRef" v-show="opened" class="vui__layer" :class="{'vui__layer-closed': closeCls}" :id="id">
<!-- //蒙版 -->
<div v-if="JSON.parse(shade)" class="vlayer__overlay" @click="shadeClicked" :style="{opacity}"></div>
<div class="vlayer__wrap" :class="[''+anim, type&&'popui__'+type, tipArrow]" :style="[layerStyle]">
<div v-if="title" class="vlayer__wrap-tit" v-html="title"></div>
<div v-if="type=='toast'&&icon" class="vlayer__toast-icon" :class="['vlayer'+icon]" v-html="toastIcon[icon]"></div>
<div class="vlayer__wrap-cntbox">
<!-- 判断插槽是否存在 -->
<template v-if="$slots.content">
<div class="vlayer__wrap-cnt"><slot name="content" /></div>
</template>
<template v-else>
<template v-if="content">
<iframe v-if="type=='iframe'" scrolling="auto" allowtransparency="true" frameborder="0" :src="content"></iframe>
<!-- message|notify|popover -->
<div v-else-if="type=='message' || type=='notify' || type=='popover'" class="vlayer__wrap-cnt">
<i v-if="icon" class="vlayer-msg__icon" :class="icon" v-html="messageIcon[icon]"></i>
<div class="vlayer-msg__group"><div v-if="title" class="vlayer-msg__title" v-html="title"></div><div v-html="content"></div></div>
</div>
<div v-else class="vlayer__wrap-cnt" v-html="content"></div>
</template>
</template>
<slot />
</div>
<div v-if="btns" class="vlayer__wrap-btns">
<span v-for="(btn,index) in btns" :key="index" class="btn" :style="btn.style" @click="btnClicked($event,index)" v-html="btn.text"></span>
</div>
<span v-if="xclose" class="vlayer__xclose" :class="!maximize&&xposition" :style="{'color': xcolor}" @click="close"></span>
<span v-if="maximize" class="vlayer__maximize" @click="maximizeClicked($event)"></span>
<span v-if="resize" class="vlayer__resize"></span>
</div>
<!-- 优化拖拽卡顿 -->
<div class="vlayer__dragfix"></div>
</div>
</template>
v3layer逻辑处理
<script>
import { onMounted, onUnmounted, ref, reactive, watch, toRefs, nextTick } from 'vue'
import domUtils from './utils/dom.js'
// 索引,蒙层控制,定时器
let $index = 0, $locknum = 0, $timer = {}, $closeTimer = null
export default {
props: {
// ...
},
emits: [
'update:modelValue'
],
setup(props, context) {
const elRef = ref(null);
const data = reactive({
opened: false,
closeCls: '',
toastIcon: {
// ...
},
messageIcon: {
// ...
},
vlayerOpts: {},
tipArrow: null,
})
onMounted(() => {
if(props.modelValue) {
open();
}
window.addEventListener('resize', autopos, false);
})
onUnmounted(() => {
window.removeEventListener('resize', autopos, false);
clearTimeout($closeTimer);
})
// 监听弹层v-model
watch(() => props.modelValue, (val) => {
// console.log('V3Layer is now [%s]', val ? 'show' : 'hide')
if(val) {
open();
}else {
close();
}
})
// 打开弹窗
const open = () => {
if(data.opened) return;
data.opened = true;
typeof props.onSuccess === 'function' && props.onSuccess();
const dom = elRef.value;
// 弹层挂载位置
if(props.teleport) {
nextTick(() => {
let teleportNode = document.querySelector(props.teleport);
teleportNode.appendChild(dom);
auto();
})
}
callback();
}
// 关闭弹窗
const close = () => {
if(!data.opened) return;
let dom = elRef.value;
let vlayero = dom.querySelector('.vlayer__wrap');
let ocnt = dom.querySelector('.vlayer__wrap-cntbox');
let omax = dom.querySelector('.vlayer__maximize');
data.closeCls = true;
clearTimeout($closeTimer);
$closeTimer = setTimeout(() => {
data.opened = false;
data.closeCls = false;
if(data.vlayerOpts.lockScroll) {
$locknum--;
if(!$locknum) {
document.body.style.paddingRight = '';
document.body.classList.remove('vui__body-hidden');
}
}
if(props.time) {
$index--;
}
// 清除弹窗样式
vlayero.style.width = vlayero.style.height = vlayero.style.top = vlayero.style.left = '';
ocnt.style.height = '';
omax && omax.classList.contains('maximized') && omax.classList.remove('maximized');
data.vlayerOpts.isBodyOverflow && (document.body.style.overflow = '');
context.emit('update:modelValue', false);
typeof props.onEnd === 'function' && props.onEnd();
}, 200)
}
// 弹窗位置
const auto = () => {
// ...
autopos();
// 全屏弹窗
if(props.fullscreen) {
full();
}
// 弹窗拖动|缩放
move();
}
const autopos = () => {
if(!data.opened) return;
let oL, oT
let pos = props.position;
let isFixed = JSON.parse(props.fixed);
let dom = elRef.value;
let vlayero = dom.querySelector('.vlayer__wrap');
if(!isFixed || props.follow) {
vlayero.style.position = 'absolute';
}
let area = [domUtils.client('width'), domUtils.client('height'), vlayero.offsetWidth, vlayero.offsetHeight]
oL = (area[0] - area[2]) / 2;
oT = (area[1] - area[3]) / 2;
if(props.follow) {
offset();
}else {
typeof pos === 'object' ? (
oL = parseFloat(pos[0]) || 0, oT = parseFloat(pos[1]) || 0
) : (
pos == 't' ? oT = 0 :
pos == 'r' ? oL = area[0] - area[2] :
pos == 'b' ? oT = area[1] - area[3] :
pos == 'l' ? oL = 0 :
pos == 'lt' ? (oL = 0, oT = 0) :
pos == 'rt' ? (oL = area[0] - area[2], oT = 0) :
pos == 'lb' ? (oL = 0, oT = area[1] - area[3]) :
pos == 'rb' ? (oL = area[0] - area[2], oT = area[1] - area[3]) :
null
)
vlayero.style.left = parseFloat(isFixed ? oL : domUtils.scroll('left') + oL) + 'px';
vlayero.style.top = parseFloat(isFixed ? oT : domUtils.scroll('top') + oT) + 'px';
}
}
// 元素跟随定位
const offset = () => {
let oW, oH, pS
let dom = elRef.value
let vlayero = dom.querySelector('.vlayer__wrap');
oW = vlayero.offsetWidth;
oH = vlayero.offsetHeight;
pS = domUtils.getFollowRect(props.follow, oW, oH);
data.tipArrow = pS[2];
vlayero.style.left = pS[0] + 'px';
vlayero.style.top = pS[1] + 'px';
}
// 最大化弹窗
const full = () => {
// ...
}
// 恢复弹窗
const restore = () => {
let dom = elRef.value;
let vlayero = dom.querySelector('.vlayer__wrap');
let otit = dom.querySelector('.vlayer__wrap-tit');
let ocnt = dom.querySelector('.vlayer__wrap-cntbox');
let obtn = dom.querySelector('.vlayer__wrap-btns');
let omax = dom.querySelector('.vlayer__maximize');
let t = otit ? otit.offsetHeight : 0
let b = obtn ? obtn.offsetHeight : 0
if(!data.vlayerOpts.lockScroll) {
data.vlayerOpts.isBodyOverflow = false;
document.body.style.overflow = '';
}
props.maximize && omax.classList.remove('maximized')
vlayero.style.left = parseFloat(data.vlayerOpts.rect[0]) + 'px';
vlayero.style.top = parseFloat(data.vlayerOpts.rect[1]) + 'px';
vlayero.style.width = parseFloat(data.vlayerOpts.rect[2]) + 'px';
vlayero.style.height = parseFloat(data.vlayerOpts.rect[3]) + 'px';
}
// 拖动|缩放弹窗
const move = () => {
// ...
}
// 事件处理
const callback = () => {
// 倒计时关闭
if(props.time) {
$index++
// 防止重复点击
if($timer[$index] !== null) clearTimeout($timer[$index])
$timer[$index] = setTimeout(() => {
close();
}, parseInt(props.time) * 1000)
}
}
// 点击最大化按钮
const maximizeClicked = (e) => {
let o = e.target
if(o.classList.contains('maximized')) {
// 恢复
restore();
} else {
// 最大化
full();
}
}
// 点击遮罩层
const shadeClicked = () => {
if(JSON.parse(props.shadeClose)) {
close();
}
}
// 按钮事件
const btnClicked = (e, index) => {
let btn = props.btns[index]
if(!btn.disabled) {
typeof btn.click === 'function' && btn.click(e)
}
}
return {
...toRefs(data),
elRef,
close,
maximizeClicked,
shadeClicked,
btnClicked,
}
}
}
</script>
大家可以在此逻辑基础上自行开发一些新功能。
okay,基于vue3开发自定义pc端弹窗组件就分享这么多,希望以上分享对大家有所帮助哈~~
React+Hooks自定义pc端弹窗组件|react.js自定义对话框
链接:https://juejin.cn/post/6913710656428457991/
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
写代码害人?抓!公安机关逮捕涉网络诈骗APP技术开发嫌疑人519名
12月31日10时,在公安部统一指挥下,北京、辽宁、湖南、广东等28个省区市公安机关同步开展集中收网行动,依法严厉打击从事涉电信网络诈骗APP技术开发的违法犯罪团伙。 截至当日15时,共捣毁违法犯罪窝点158个,抓获违法犯罪嫌疑人519名。
当前,随着信息技术快速发展,各类APP已成为满足人民群众获取信息、投资理财、贷款转账等各类需求的重要渠道,在给日常生产生活带来便利的同时,也被一些不法分子用于违法犯罪活动。公安机关在工作中发现,一些电信网络诈骗犯罪团伙为提高诈骗成功率,打着正规平台的旗号诱导受害人下载虚假APP,并诱骗受害人在该APP上进行投资理财、申请贷款等,进而骗取受害人钱款。
今年以来,通过虚假APP实施的电信网络诈骗案件高发多发,严重危害人民群众财产安全和社会经济秩序。公安部对此高度重视,深入研判该类案件规律特点,对涉电信网络诈骗APP的开发、封装、应用各个环节开展分析研究,部署开展专案侦办,要求全链条打击相关黑灰产业。
经专案组缜密侦查,公安机关梳理出一批违法犯罪线索,涉及全国24个省区市,并初步掌握了大量犯罪事实和证据。在此基础上,公安部部署对从事涉电信网络诈骗APP技术开发的违法犯罪团伙开展集中抓捕行动,严打团伙,捣毁窝点,斩断链条,切实从源头上遏制电信网络诈骗犯罪的高发态势。
据了解,诈骗团伙利用虚假APP实施的电信网络诈骗案件占该类案件约60%,虚假APP已成为整个犯罪链条中不可或缺的关键环节,并由此催生出一个庞大的技术开发灰色产业链,大量违法犯罪人员参与其中。
相关技术开发人员分工明确、团伙作案,围绕电信网络诈骗犯罪团伙的具体需求“量身定制”APP各种诈骗功能,有的负责编写程序代码,有的负责购买域名和租用服务器,有的负责APP的封装和分发。经各环节层层运作,最终将虚假APP贩卖给诈骗团伙。 随后,诈骗团伙根据其不法目的和APP功能特点,将其包装成极具迷惑性的“正规”应用平台,诱骗受害人点击链接或扫描二维码下载APP,进而实施诈骗。
公安部有关负责人表示,打击涉电信网络诈骗APP技术开发违法犯罪活动,是全链条打击电信网络诈骗黑灰产业的重要举措。公安机关将继续保持对此类犯罪的严打高压态势,坚决斩断为电信诈骗等违法犯罪提供技术、资金等各类非法服务的犯罪链条,切实维护人民群众财产安全和合法权益。 同时,公安机关提醒广大群众增强防范意识,对于未知来源的APP不下载、不点击、不扫码,切实守护好自己的钱袋子。
此文章转载自央视新闻,如有不当联系删除。原文链接
12月31日10时,在公安部统一指挥下,北京、辽宁、湖南、广东等28个省区市公安机关同步开展集中收网行动,依法严厉打击从事涉电信网络诈骗APP技术开发的违法犯罪团伙。 截至当日15时,共捣毁违法犯罪窝点158个,抓获违法犯罪嫌疑人519名。
当前,随着信息技术快速发展,各类APP已成为满足人民群众获取信息、投资理财、贷款转账等各类需求的重要渠道,在给日常生产生活带来便利的同时,也被一些不法分子用于违法犯罪活动。公安机关在工作中发现,一些电信网络诈骗犯罪团伙为提高诈骗成功率,打着正规平台的旗号诱导受害人下载虚假APP,并诱骗受害人在该APP上进行投资理财、申请贷款等,进而骗取受害人钱款。
今年以来,通过虚假APP实施的电信网络诈骗案件高发多发,严重危害人民群众财产安全和社会经济秩序。公安部对此高度重视,深入研判该类案件规律特点,对涉电信网络诈骗APP的开发、封装、应用各个环节开展分析研究,部署开展专案侦办,要求全链条打击相关黑灰产业。
经专案组缜密侦查,公安机关梳理出一批违法犯罪线索,涉及全国24个省区市,并初步掌握了大量犯罪事实和证据。在此基础上,公安部部署对从事涉电信网络诈骗APP技术开发的违法犯罪团伙开展集中抓捕行动,严打团伙,捣毁窝点,斩断链条,切实从源头上遏制电信网络诈骗犯罪的高发态势。
据了解,诈骗团伙利用虚假APP实施的电信网络诈骗案件占该类案件约60%,虚假APP已成为整个犯罪链条中不可或缺的关键环节,并由此催生出一个庞大的技术开发灰色产业链,大量违法犯罪人员参与其中。
相关技术开发人员分工明确、团伙作案,围绕电信网络诈骗犯罪团伙的具体需求“量身定制”APP各种诈骗功能,有的负责编写程序代码,有的负责购买域名和租用服务器,有的负责APP的封装和分发。经各环节层层运作,最终将虚假APP贩卖给诈骗团伙。 随后,诈骗团伙根据其不法目的和APP功能特点,将其包装成极具迷惑性的“正规”应用平台,诱骗受害人点击链接或扫描二维码下载APP,进而实施诈骗。
公安部有关负责人表示,打击涉电信网络诈骗APP技术开发违法犯罪活动,是全链条打击电信网络诈骗黑灰产业的重要举措。公安机关将继续保持对此类犯罪的严打高压态势,坚决斩断为电信诈骗等违法犯罪提供技术、资金等各类非法服务的犯罪链条,切实维护人民群众财产安全和合法权益。 同时,公安机关提醒广大群众增强防范意识,对于未知来源的APP不下载、不点击、不扫码,切实守护好自己的钱袋子。
此文章转载自央视新闻,如有不当联系删除。原文链接
收起阅读 »建议权限配置像apicloud一样可视化选择,因为华为个操蛋的东西让填说明,得看半天,然后万一不对又要从新的打包看权限,导致资源浪费
建议权限配置像apicloud一样可视化选择,因为华为个操蛋的东西让填说明,得看半天,然后万一不对又要从新的打包看权限,导致资源浪费
建议权限配置像apicloud一样可视化选择,因为华为个操蛋的东西让填说明,得看半天,然后万一不对又要从新的打包看权限,导致资源浪费
云端打包 提示没有剩余空间
Caused by: com.android.tools.r8.utils.AbortException: Error: 安装包制作目录/app/build/intermediates/transforms/dexBuilder/release/59.jar, No space left on device
at com.android.tools.r8.utils.Reporter.failIfPendingErrors(Reporter.java:116)
at com.android.tools.r8.dex.ApplicationWriter.write(ApplicationWriter.java:258)
at com.android.tools.r8.D8.run(D8.java:179)
at com.android.tools.r8.D8.lambda$run$1(D8.java:93)
at com.android.tools.r8.utils.ExceptionUtils.withCompilationHandler(ExceptionUtils.java:55)
Caused by: com.android.tools.r8.utils.AbortException: Error: 安装包制作目录/app/build/intermediates/transforms/dexBuilder/release/59.jar, No space left on device
at com.android.tools.r8.utils.Reporter.failIfPendingErrors(Reporter.java:116)
at com.android.tools.r8.dex.ApplicationWriter.write(ApplicationWriter.java:258)
at com.android.tools.r8.D8.run(D8.java:179)
at com.android.tools.r8.D8.lambda$run$1(D8.java:93)
at com.android.tools.r8.utils.ExceptionUtils.withCompilationHandler(ExceptionUtils.java:55)
IOS 微信分享 -95的报错
var b = new plus.nativeObj.Bitmap('bitblmap');
b.loadBase64Data(that.sImg, function() {
var fileName = '_doc/' + randomString(10) + '.jpg';
b.save(fileName, {
overwrite: true
}, function(object) {
plus.gallery.save(fileName, function(obj) {
** vue.shareImgSrc = obj.file || obj.path;**
// mui.toast('已保存到手机相册中');
vue.showTips = false;
mui('#sheet1').popover('toggle');
shareImage()
}, function() {
mui.toast('save fail');
vue.showTips = false;
mui('#sheet1').popover('toggle');
});
}, function() {
mui.toast('fail');
vue.showTips = false;
mui('#sheet1').popover('toggle');
});
}, function() {
mui.toast('图片创建失败');
vue.showTips = false;
mui('#sheet1').popover('toggle');
});
主要是IOS保存图片后返回的字段为path,安卓下为file.设置正确后,微信好友和朋友圈均可以分享
var b = new plus.nativeObj.Bitmap('bitblmap');
b.loadBase64Data(that.sImg, function() {
var fileName = '_doc/' + randomString(10) + '.jpg';
b.save(fileName, {
overwrite: true
}, function(object) {
plus.gallery.save(fileName, function(obj) {
** vue.shareImgSrc = obj.file || obj.path;**
// mui.toast('已保存到手机相册中');
vue.showTips = false;
mui('#sheet1').popover('toggle');
shareImage()
}, function() {
mui.toast('save fail');
vue.showTips = false;
mui('#sheet1').popover('toggle');
});
}, function() {
mui.toast('fail');
vue.showTips = false;
mui('#sheet1').popover('toggle');
});
}, function() {
mui.toast('图片创建失败');
vue.showTips = false;
mui('#sheet1').popover('toggle');
});
主要是IOS保存图片后返回的字段为path,安卓下为file.设置正确后,微信好友和朋友圈均可以分享
收起阅读 »android手机wifi调试详细教程(非root)
看了官方给的教程,有几点说的不是太详细,结合百度的一些内容,最后总算成功了。
为了让大家少走弯路,在此记录下详细的步骤:
- 电脑和手机需要连接同一个wifi
- 手机与电脑用USB数据线连接(注意手机是开发者模式)
- 进入到HbuliderX的安装目录: HBuilderX\plugins\launcher\tools\adbs后SHIFT+鼠标右击,在此处打开命令窗口执行 adb tcpip 5555
- 执行成功后提示:restarting in TCP mode port: 5555
- 断开USB数据线连接,执行 adb connect 192.168.13.104:5555(注意斜体加粗是你手机的ip)
- 手机的ip:手机连接的wifi,点开连接wifi的详细信息即可查到
看了官方给的教程,有几点说的不是太详细,结合百度的一些内容,最后总算成功了。
为了让大家少走弯路,在此记录下详细的步骤:
- 电脑和手机需要连接同一个wifi
- 手机与电脑用USB数据线连接(注意手机是开发者模式)
- 进入到HbuliderX的安装目录: HBuilderX\plugins\launcher\tools\adbs后SHIFT+鼠标右击,在此处打开命令窗口执行 adb tcpip 5555
- 执行成功后提示:restarting in TCP mode port: 5555
- 断开USB数据线连接,执行 adb connect 192.168.13.104:5555(注意斜体加粗是你手机的ip)
- 手机的ip:手机连接的wifi,点开连接wifi的详细信息即可查到
Webview历史记录返回
开发中遇到可能打开一个webview内会进行多次页面跳转的问题
1.监听onBackPress事件
onBackPress(e) {
if (e.from === 'navigateBack') {
return;
}
this.checkcanBack();
return true;
},
2.同步等待判断此处是否webview可返回,如果没有历史记录,关闭当前页面
async checkcanBack() {
let ret = await this.canBack();
if (ret) {
//wv为创建的webview对象,全局变量保存
wv.back();
} else {
uni.navigateBack()
}
},
canBack() {
return new Promise((resolve, reject) => {
//wv为创建的webview对象,全局变量保存
wv.canBack(function(e) {
if (e.canBack) {
resolve(true); //后退到上次加载的页面
} else {
resolve(false);
}
});
});
},
开发中遇到可能打开一个webview内会进行多次页面跳转的问题
1.监听onBackPress事件
onBackPress(e) {
if (e.from === 'navigateBack') {
return;
}
this.checkcanBack();
return true;
},
2.同步等待判断此处是否webview可返回,如果没有历史记录,关闭当前页面
async checkcanBack() {
let ret = await this.canBack();
if (ret) {
//wv为创建的webview对象,全局变量保存
wv.back();
} else {
uni.navigateBack()
}
},
canBack() {
return new Promise((resolve, reject) => {
//wv为创建的webview对象,全局变量保存
wv.canBack(function(e) {
if (e.canBack) {
resolve(true); //后退到上次加载的页面
} else {
resolve(false);
}
});
});
},
元旦特惠➕提成 专业开发--手机App、公众号、小程序、电商平台、购物商城、分销系统、企业官网、管理系统、等程序-
元旦特惠➕提成
专业开发--手机App、公众号、小程序、电商平台、购物商城、分销系统、企业官网、管理系统、等程序--无论你是公司、团队、个人、手中有不做,或者感觉自己做不挣钱的项目可以转包给我们,如有幸签订合同后,我们公司会拿出项目总金额的10%作为提成(10w提成1w,1w提成1000元,1000元提成100)不管大小单子,推荐给我们就有提成。VX: web9688 诚信经营

元旦特惠➕提成
专业开发--手机App、公众号、小程序、电商平台、购物商城、分销系统、企业官网、管理系统、等程序--无论你是公司、团队、个人、手中有不做,或者感觉自己做不挣钱的项目可以转包给我们,如有幸签订合同后,我们公司会拿出项目总金额的10%作为提成(10w提成1w,1w提成1000元,1000元提成100)不管大小单子,推荐给我们就有提成。VX: web9688 诚信经营

小程序的某个人经验(小白)
小程序调试时,一定不要太依赖(不校验合法域名)的选项.
1.你最终一定会去绑定合法域名的.
2. 在你开发中, 你会渐渐忽视某项可能在最终很小的问题, 也就是说当你勾选不校验合法域名时, 会碰到的一些bug. 因为它(微信开发工具)不会给你提示域名的错误.
总之, 任何事都一样, 需要养成习惯, 一开始解决好小问题, 会给以后避免很大的bug.
小程序调试时,一定不要太依赖(不校验合法域名)的选项.
1.你最终一定会去绑定合法域名的.
2. 在你开发中, 你会渐渐忽视某项可能在最终很小的问题, 也就是说当你勾选不校验合法域名时, 会碰到的一些bug. 因为它(微信开发工具)不会给你提示域名的错误.
总之, 任何事都一样, 需要养成习惯, 一开始解决好小问题, 会给以后避免很大的bug.
收起阅读 »vue,nvue视频组件不能预加载,自动缓冲,播放性能弱与小程序和h5,望解决
vue,nvue视频组件不能预加载,自动缓冲,播放性能弱与小程序和h5,像html5有 preload="auto" 属性自动加载一小段
看过大部分开发者都是选择webview方式播放解决
望解决
vue,nvue视频组件不能预加载,自动缓冲,播放性能弱与小程序和h5,像html5有 preload="auto" 属性自动加载一小段
看过大部分开发者都是选择webview方式播放解决
望解决




