1
This commit is contained in:
@@ -0,0 +1,985 @@
|
||||
<script lang="ts" setup>
|
||||
import { getCurrentInstance, ref, computed, watch, onMounted, onBeforeUnmount } from "vue"
|
||||
import { checkIsCssUnit, getUid, getUnit } from "../../core/util/xCoreUtil.uts"
|
||||
import { getDefaultColor, colorAddDeepen } from "../../core/util/xCoreColorUtil.uts"
|
||||
import { xConfig,xProvitae } from "../../config/xConfig.uts"
|
||||
|
||||
type callbackType = ()=>Promise<boolean>;
|
||||
type positionType = "top" | "bottom" | "left" | "right"
|
||||
|
||||
/**
|
||||
* @name 抽屉 xDrawer
|
||||
* @description 提供四个方向的弹出。
|
||||
* @page /pages/index/drawer
|
||||
* @category 反馈组件
|
||||
* @constant 平台兼容
|
||||
* | Harmony | H5 | andriod | IOS | 小程序 | UTS | UNIAPP-X SDK | version |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| ☑ | ☑ | ☑️ | ☑️ | ☑️ | ☑️ | 4.76+ | 1.1.18 |
|
||||
*/
|
||||
defineOptions({name:"xDrawer"})
|
||||
|
||||
const i18n = xConfig.i18n
|
||||
const proxy = getCurrentInstance()?.proxy??null;
|
||||
defineSlots<{
|
||||
trigger(props: { show: boolean }): any
|
||||
}>()
|
||||
|
||||
const emits = defineEmits([
|
||||
/**
|
||||
* 点击遮罩事件
|
||||
*/
|
||||
'click',
|
||||
/**
|
||||
* 关闭是触发
|
||||
*/
|
||||
'close',
|
||||
/**
|
||||
* 打开时触发
|
||||
*/
|
||||
'open',
|
||||
/**
|
||||
* 打开前执行
|
||||
*/
|
||||
'beforeOpen',
|
||||
/**
|
||||
* 关闭前执行
|
||||
*/
|
||||
'beforeClose',
|
||||
/**
|
||||
* 等同v-model:show
|
||||
*/
|
||||
'update:show',
|
||||
/**
|
||||
* 取消时触发
|
||||
*/
|
||||
'cancel',
|
||||
/**
|
||||
* 确认时触发
|
||||
*/
|
||||
'confirm'
|
||||
])
|
||||
|
||||
type xDrawerPropsType = {
|
||||
/**
|
||||
* 自定义遮罩样式
|
||||
*/
|
||||
customStyle: string,
|
||||
/**
|
||||
* 自定义容器背景层样式
|
||||
*/
|
||||
customWrapStyle: string,
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
title: string,
|
||||
/**
|
||||
* 显示底部操作栏
|
||||
*/
|
||||
showFooter: boolean,
|
||||
/**
|
||||
* 是否显示标题
|
||||
*/
|
||||
showTitle: boolean,
|
||||
/**
|
||||
* 是否显示底部关闭按钮
|
||||
*/
|
||||
showClose: boolean,
|
||||
/**
|
||||
* 遮罩是否允许点击被关闭
|
||||
*/
|
||||
overlayClick: boolean,
|
||||
/**
|
||||
* 显示可v-model:show双向绑定
|
||||
*/
|
||||
show: boolean,
|
||||
/**
|
||||
* 显示取消按钮
|
||||
*/
|
||||
showCancel: boolean,
|
||||
/**
|
||||
* 取消按钮的文本
|
||||
*/
|
||||
cancelText: string,
|
||||
/**
|
||||
* 确认按钮的文本
|
||||
*/
|
||||
confirmText: string,
|
||||
/**
|
||||
* 动画时间
|
||||
*/
|
||||
duration: number,
|
||||
/**
|
||||
* 打开dom的延迟量,如果你打开 弹窗在ios正常。
|
||||
* 请不要修改此值。如果遇到打不开,或者 打开 后没动画,关闭不了等可能是sdk bug导致
|
||||
* 此时需要加大值来避免。具体加多少以你弹窗内的节点复杂度有关,需要你自行压力测试。
|
||||
* 此值仅在ios下生效。
|
||||
*/
|
||||
watiDuration: number,
|
||||
/**
|
||||
* 打开方向。
|
||||
*/
|
||||
position: positionType,
|
||||
/**
|
||||
* 打开方向为上和下时的圆角
|
||||
* 空值时,取全局配置的圆角。
|
||||
*/
|
||||
round: string,
|
||||
/**
|
||||
* 左右时为内容宽,
|
||||
* 上下时为内容高
|
||||
* 百分比,数字字符或者带单位,或者为auto(根据内容自动高度或者宽高)
|
||||
*/
|
||||
size: string,
|
||||
/**
|
||||
* 弹层最大的高度值,默认为屏幕的可视高
|
||||
* 提供值时不能为百分比,可以是px,rpx单位数字。如果你不带单位,默认转换为rpx单位。
|
||||
*/
|
||||
maxHeight: string,
|
||||
/**
|
||||
* 背景颜色
|
||||
*/
|
||||
bgColor: string,
|
||||
/**
|
||||
* 暗黑背景颜色,如果不提供默认读取全局的sheet配置
|
||||
*/
|
||||
darkBgColor: string,
|
||||
/**
|
||||
* 遮罩的背景色
|
||||
*/
|
||||
overflayBgColor: string,
|
||||
/**
|
||||
* 是否禁用内部的scroll标签
|
||||
* 禁用后内容不会滚动,如果设定了指定高,内容超出指定高,会被裁切
|
||||
* 但如果没有指定高,内容自动的话,高是自动的。
|
||||
*/
|
||||
disabledScroll: boolean,
|
||||
/**
|
||||
* 内容区域左右和下的边距。
|
||||
*/
|
||||
contentMargin: string,
|
||||
/**
|
||||
* 宽屏时是否让内容剧中显示
|
||||
* 并限制其宽为屏幕宽,只展示中间内容以适应宽屏。
|
||||
* 注意只有top,bottom才会生效。
|
||||
*/
|
||||
widthCoverCenter: boolean,
|
||||
/**
|
||||
* 滑动左右或者上下关闭弹出层
|
||||
* 注意如果设置为0就表示关闭该功能。
|
||||
* 默认drawer嵌套了scroll-view,再你滚动到顶或者底时,如果继续滑动的距离大于此值关闭层。
|
||||
* 但如果你是禁用了内部scroll-view,而是采用自己的scorll-view,此时该功能会与你的滚动手势冲突,请自行考虑。
|
||||
* 建议要打开时设置为80-100比较合理
|
||||
*/
|
||||
swiperLenClose: number,
|
||||
/**
|
||||
* 距离顶部的偏移量,如果你布局顶会遮罩弹层可以考虑使用此值
|
||||
*/
|
||||
offsetTop: string,
|
||||
/**
|
||||
* 距离底部的偏移量,如果你布局底会遮罩弹层可以考虑使用此值
|
||||
*/
|
||||
offsetBottom: string,
|
||||
/**
|
||||
* 弹层的层级
|
||||
*/
|
||||
zIndex: number,
|
||||
/**
|
||||
* 懒加载
|
||||
* 为了解决业务布局节点超多时,你可能需要内容延迟加载以免阻塞动画流畅度.
|
||||
* 如果你启用了lazy,每次打开时,动画执行后才会显示内容.这样动画就流畅,不会因为节点过多造成的卡.
|
||||
* 开启了此属性后ios端前面的watiDuration属性可以不用再设置了.
|
||||
*/
|
||||
lazy: boolean,
|
||||
/**
|
||||
* 是否禁用确认按钮
|
||||
*/
|
||||
disabledConfirm: boolean,
|
||||
/**
|
||||
* 底部按钮操作的主题色,空取全局
|
||||
*/
|
||||
btnColor: string,
|
||||
/**
|
||||
* 关闭前异步执行的函数,如果返回false阻止关闭,返回true允许关闭
|
||||
* 必须返回的是Promise异步函数,且类型返回值必须是Promise<boolean>,不然会报错。
|
||||
*/
|
||||
beforeClose: callbackType,
|
||||
/**
|
||||
* 关闭图标的颜色
|
||||
*/
|
||||
closeColor: string,
|
||||
/**
|
||||
* 关闭图标的暗黑颜色
|
||||
*/
|
||||
closeDarkColor: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<xDrawerPropsType>(), {
|
||||
customStyle: "",
|
||||
customWrapStyle: "",
|
||||
title: "",
|
||||
showFooter: false,
|
||||
showTitle: true,
|
||||
showClose: false,
|
||||
overlayClick: true,
|
||||
show: false,
|
||||
showCancel: true,
|
||||
cancelText: "",
|
||||
confirmText: "",
|
||||
duration: 300,
|
||||
watiDuration: 120,
|
||||
position: "bottom",
|
||||
round: "",
|
||||
size: "50%",
|
||||
maxHeight: "",
|
||||
bgColor: 'white',
|
||||
darkBgColor: '',
|
||||
overflayBgColor: 'rgba(0, 0, 0, 0.4)',
|
||||
disabledScroll: false,
|
||||
contentMargin: '16',
|
||||
widthCoverCenter: false,
|
||||
swiperLenClose: 0,
|
||||
offsetTop: '0',
|
||||
offsetBottom: '0',
|
||||
zIndex: 1100,
|
||||
lazy: false,
|
||||
disabledConfirm: false,
|
||||
btnColor: "",
|
||||
beforeClose: () : Promise<boolean> => {
|
||||
return Promise.resolve(true)
|
||||
},
|
||||
closeColor: "#e6e6e6",
|
||||
closeDarkColor: "#545454"
|
||||
})
|
||||
|
||||
// 响应式数据
|
||||
const _width = ref(0)
|
||||
const _height = ref(0)
|
||||
const showOverflay = ref(false)
|
||||
const actioning = ref(false)
|
||||
const status = ref("")
|
||||
const id = ref("xDrawer" + getUid())
|
||||
const wrapId = ref("xDrawerWrap" + getUid())
|
||||
const first = ref(true)
|
||||
const tid = ref(0)
|
||||
const windtop = ref(0)
|
||||
const windowBottom = ref(0)
|
||||
const start_move_x = ref(0)
|
||||
const start_move_y = ref(0)
|
||||
const move_x = ref(0)
|
||||
const move_y = ref(0)
|
||||
const move_end_x = ref(0)
|
||||
const move_end_y = ref(0)
|
||||
const scrollTop = ref(-1)
|
||||
const isTopOrBottomByScroll = ref(false)
|
||||
const xDrawerContentHeight = ref(0)
|
||||
const safeFooterHeight = ref(0)
|
||||
const lezyShowModal = ref(true)
|
||||
const isOpenedDefault = ref(false)
|
||||
const isLoading = ref(false)
|
||||
const anitid = ref(23)
|
||||
// #ifdef H5
|
||||
const teleportElH5 = ref("uni-app")
|
||||
const teleportTarget = ref<string | null>(null)
|
||||
const getTeleportTarget = () => {
|
||||
try {
|
||||
if(status.value == ''||status.value=='close') return 'uni-app'
|
||||
// 优先尝试 uni-page
|
||||
if (document.querySelector('uni-page')) {
|
||||
return 'uni-page'
|
||||
}
|
||||
// 优先尝试 uni-app
|
||||
if (document.querySelector('uni-app')) {
|
||||
return 'uni-app'
|
||||
}
|
||||
// 备用方案:尝试 app
|
||||
if (document.querySelector('#app')) {
|
||||
return '#app'
|
||||
}
|
||||
// 最后备用:body
|
||||
return 'body'
|
||||
} catch (error) {
|
||||
// console.warn('Failed to get teleport target:', error)
|
||||
return 'body'
|
||||
}
|
||||
}
|
||||
|
||||
// 检查teleport目标是否可用
|
||||
const isTeleportTargetValid = (target: string) => {
|
||||
try {
|
||||
return !!document.querySelector(target)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
// 计算属性
|
||||
const _disabledConfirm = computed((): boolean => props.disabledConfirm)
|
||||
const _lazy = computed((): boolean => props.lazy)
|
||||
const _customStyle = computed((): string => props.customStyle)
|
||||
const _customWrapStyle = computed((): string => props.customWrapStyle)
|
||||
const _show = computed((): boolean => props.show)
|
||||
const _widthCoverCenter = computed((): boolean => props.widthCoverCenter)
|
||||
const _showClose = computed((): boolean => props.showClose)
|
||||
const _duration = computed((): number => props.duration)
|
||||
const _position = computed((): string => props.position)
|
||||
const _showTitle = computed((): boolean => props.showTitle)
|
||||
|
||||
const _round = computed((): string => {
|
||||
let round = props.round;
|
||||
if (round == "") {
|
||||
round = xConfig.drawerRadius
|
||||
}
|
||||
let radius = checkIsCssUnit(round, xConfig.unit);
|
||||
|
||||
let _r = "none"
|
||||
if (props.position == 'top') {
|
||||
_r = `0px 0px ${radius} ${radius}`
|
||||
}
|
||||
if (props.position == 'bottom') {
|
||||
_r = `${radius} ${radius} 0px 0px`
|
||||
}
|
||||
return _r
|
||||
})
|
||||
|
||||
const _offset = computed((): number => {
|
||||
let offset = checkIsCssUnit(props.offsetTop, xConfig.unit);
|
||||
let x = parseFloat(offset)
|
||||
let unit = getUnit(offset)
|
||||
if (unit == 'rpx') {
|
||||
x = uni.rpx2px(x)
|
||||
}
|
||||
return x;
|
||||
})
|
||||
|
||||
const _offsetBottom = computed((): number => {
|
||||
let offset = checkIsCssUnit(props.offsetBottom, xConfig.unit);
|
||||
let x = parseFloat(offset)
|
||||
let unit = getUnit(offset)
|
||||
if (unit == 'rpx') {
|
||||
x = uni.rpx2px(x)
|
||||
}
|
||||
return x;
|
||||
})
|
||||
|
||||
const _size = computed((): string => checkIsCssUnit(props.size, xConfig.unit))
|
||||
const _contentMargin = computed((): string => checkIsCssUnit(props.contentMargin, xConfig.unit))
|
||||
const _showFooter = computed((): boolean => props.showFooter)
|
||||
|
||||
const _maxHeight = computed((): string => {
|
||||
if (props.maxHeight == "") return ""
|
||||
if (props.position == 'left' || props.position == 'right') return ""
|
||||
return checkIsCssUnit(props.maxHeight, xConfig.unit);
|
||||
})
|
||||
|
||||
const _showCancel = computed((): boolean => props.showCancel)
|
||||
|
||||
const _title = computed((): string => {
|
||||
if(props.title==''){
|
||||
return i18n.t("tmui4x.modal.title")
|
||||
}
|
||||
return props.title
|
||||
})
|
||||
|
||||
const _cancelText = computed((): string => {
|
||||
if(props.cancelText==''){
|
||||
return i18n.t("tmui4x.cancel")
|
||||
}
|
||||
return props.cancelText
|
||||
})
|
||||
|
||||
const _confirmText = computed((): string => {
|
||||
if(props.confirmText==''){
|
||||
return i18n.t("tmui4x.confirm")
|
||||
}
|
||||
return props.confirmText
|
||||
})
|
||||
|
||||
const _animationFun = computed((): string => xConfig.animationFun)
|
||||
|
||||
const _bgColor = computed((): string => {
|
||||
let bgcolor = props.bgColor;
|
||||
if (xConfig.dark == 'dark') {
|
||||
if (props.darkBgColor != '') {
|
||||
|
||||
}
|
||||
bgcolor = props.darkBgColor != '' ? props.darkBgColor : xConfig.sheetDarkColor
|
||||
}
|
||||
return getDefaultColor(bgcolor)
|
||||
})
|
||||
|
||||
const _btnColor = computed((): string => {
|
||||
if(props.btnColor == '') return getDefaultColor(xConfig.color)
|
||||
return getDefaultColor(props.btnColor)
|
||||
})
|
||||
|
||||
const __height = computed((): string => {
|
||||
let h = '100%';
|
||||
// #ifdef WEB
|
||||
h = `calc(100% - ${windtop.value}px - ${_offsetBottom.value}px)`
|
||||
// #endif
|
||||
// #ifdef APP || MP-WEIXIN
|
||||
if (_offset.value > 0 || _offsetBottom.value > 0) {
|
||||
h = (_height.value - _offsetBottom.value) + 'px'
|
||||
}
|
||||
// #endif
|
||||
return h;
|
||||
})
|
||||
|
||||
const _titleFontSize = computed((): string => (xConfig.fontScale * 16).toString() + 'px')
|
||||
const _isDark = computed((): boolean => xConfig.dark == 'dark')
|
||||
const _closeIcon = computed((): string => xConfig.closeIcon)
|
||||
function onEnd() {
|
||||
actioning.value = false;
|
||||
if (status.value == 'close') {
|
||||
showOverflay.value = false;
|
||||
/**
|
||||
* 关闭时执行
|
||||
*/
|
||||
emits('close')
|
||||
/**
|
||||
* 等同v-model:show
|
||||
*/
|
||||
emits('update:show', false)
|
||||
if (_lazy.value) {
|
||||
lezyShowModal.value = false
|
||||
}
|
||||
// #ifdef WEB
|
||||
teleportTarget.value = getTeleportTarget()
|
||||
// #endif
|
||||
} else {
|
||||
/**
|
||||
* 打开执行的事件
|
||||
*/
|
||||
emits('open')
|
||||
if (_lazy.value) {
|
||||
lezyShowModal.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setStyleAni() {
|
||||
try {
|
||||
let sys = uni.getWindowInfo()
|
||||
_height.value = sys.windowHeight - _offset.value
|
||||
let watiDuration = 60;
|
||||
// #ifdef APP-IOS
|
||||
watiDuration = props.watiDuration
|
||||
// #endif
|
||||
|
||||
if (status.value == 'open') {
|
||||
showOverflay.value = true;
|
||||
clearTimeout(tid.value)
|
||||
tid.value = setTimeout(function () {
|
||||
let element = proxy!.$refs['xDrawerWrap'] as UniElement | null
|
||||
let elementWrap = proxy!.$refs['xDrawerWrapContent'] as UniElement | null
|
||||
if (element== null || elementWrap == null) return;
|
||||
|
||||
element!.style.setProperty("transition-duration", _duration.value.toString() + 'ms')
|
||||
elementWrap!.style.setProperty("transition-duration", _duration.value.toString() + 'ms')
|
||||
|
||||
element!.style.setProperty('opacity', 1)
|
||||
elementWrap!.style.setProperty('transform', `translate(0%,0%)`)
|
||||
}, watiDuration);
|
||||
} else if (status.value == 'close') {
|
||||
let element = proxy!.$refs['xDrawerWrap'] as UniElement | null
|
||||
let elementWrap = proxy!.$refs['xDrawerWrapContent'] as UniElement | null
|
||||
if (element== null || elementWrap == null) return;
|
||||
|
||||
element.style.setProperty("transition-duration", _duration.value.toString() + 'ms')
|
||||
elementWrap.style.setProperty("transition-duration", _duration.value.toString() + 'ms')
|
||||
|
||||
element.style.setProperty('opacity', 0)
|
||||
if (_position.value == 'bottom') {
|
||||
elementWrap.style.setProperty('transform', `translate(0%,100%)`)
|
||||
} else if (_position.value == 'top') {
|
||||
elementWrap.style.setProperty('transform', `translate(0%,-100%)`)
|
||||
} else if (_position.value == 'left') {
|
||||
elementWrap.style.setProperty('transform', `translate(-100%,0%)`)
|
||||
} else if (_position.value == 'right') {
|
||||
elementWrap.style.setProperty('transform', `translate(100%,0%)`)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
//TODO handle the exception
|
||||
}
|
||||
}
|
||||
|
||||
function overflayMoveTouch(evt : TouchEvent) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
|
||||
|
||||
function closeAlert() {
|
||||
if (actioning.value) return;
|
||||
if (status.value == 'close') return;
|
||||
actioning.value = true;
|
||||
status.value = 'close'
|
||||
|
||||
/**
|
||||
* 关闭前执行
|
||||
*/
|
||||
emits('beforeClose')
|
||||
setStyleAni();
|
||||
anitid.value = setTimeout(function() {
|
||||
onEnd()
|
||||
}, _duration.value);
|
||||
}
|
||||
|
||||
function showAlert() {
|
||||
if (actioning.value) return;
|
||||
if (status.value == 'open') return;
|
||||
|
||||
showOverflay.value = true;
|
||||
actioning.value = true;
|
||||
status.value = 'open'
|
||||
|
||||
// #ifdef WEB
|
||||
teleportTarget.value = getTeleportTarget()
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* 打开前执行
|
||||
*/
|
||||
emits('beforeOpen')
|
||||
setStyleAni();
|
||||
anitid.value = setTimeout(function() {
|
||||
onEnd()
|
||||
}, _duration.value+60);
|
||||
}
|
||||
|
||||
function onClickOverflowy(evt : Event) {
|
||||
evt.stopPropagation()
|
||||
/**
|
||||
* 点击遮罩事件
|
||||
*/
|
||||
emits("click")
|
||||
if (!props.overlayClick||isLoading.value) return;
|
||||
closeAlert();
|
||||
}
|
||||
|
||||
// 方法
|
||||
function cancelEvt() {
|
||||
// ios渲染有时会造成无法触发onEnd事件,导致无法关闭。这是ios渲染的bug造成,无力修复,已向官方反馈
|
||||
// 但这牵涉到底层问题。一时无法修复,故在ios特殊处理。后期修复,需要删除此值。
|
||||
// #ifdef APP-IOS
|
||||
actioning.value = false;
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* 取消时触发
|
||||
*/
|
||||
emits('cancel')
|
||||
closeAlert()
|
||||
}
|
||||
|
||||
async function confirmEvt(): Promise<any> {
|
||||
isLoading.value = true;
|
||||
let isCanClose = await props.beforeClose()
|
||||
isLoading.value = false;
|
||||
if(!isCanClose){
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
// ios渲染有时会造成无法触发onEnd事件,导致无法关闭。这是ios渲染的bug造成,无力修复,已向官方反馈
|
||||
// 但这牵涉到底层问题。一时无法修复,故在ios特殊处理。后期修复,需要删除此值。
|
||||
// #ifdef APP-IOS
|
||||
actioning.value = false;
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* 确认时触发
|
||||
*/
|
||||
emits('confirm')
|
||||
closeAlert()
|
||||
return Promise.resolve(false)
|
||||
}
|
||||
|
||||
|
||||
function openDrawer() {
|
||||
showAlert();
|
||||
}
|
||||
|
||||
|
||||
function swiperClose() {
|
||||
let offsetX = move_end_x.value - start_move_x.value
|
||||
let offsetY = move_end_y.value - start_move_y.value
|
||||
|
||||
if (props.swiperLenClose == 0 || (actioning.value && status.value == 'close')) return;
|
||||
|
||||
if (props.position == 'left' && offsetX < props.swiperLenClose * -1 && Math.abs(offsetX) >= Math.abs(offsetY)) {
|
||||
closeAlert()
|
||||
}
|
||||
if (props.position == 'right' && offsetX > props.swiperLenClose && Math.abs(offsetX) >= Math.abs(offsetY)) {
|
||||
closeAlert()
|
||||
}
|
||||
if (props.position == 'top' && offsetY < props.swiperLenClose * -1 && Math.abs(offsetY) >= Math.abs(offsetX)) {
|
||||
closeAlert()
|
||||
}
|
||||
if (props.position == 'bottom' && offsetY > props.swiperLenClose && Math.abs(offsetY) >= Math.abs(offsetX)) {
|
||||
closeAlert()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function maskerMove(evt : UniTouchEvent) {
|
||||
// #ifdef WEB
|
||||
evt.preventDefault()
|
||||
// #endif
|
||||
}
|
||||
|
||||
function mStart(evt : UniTouchEvent) {
|
||||
if (props.swiperLenClose == 0) return;
|
||||
start_move_x.value = evt.changedTouches[0].clientX
|
||||
start_move_y.value = evt.changedTouches[0].clientY
|
||||
}
|
||||
|
||||
function mMove(evt : UniTouchEvent) {
|
||||
if (props.swiperLenClose == 0) return;
|
||||
// #ifdef WEB
|
||||
evt.preventDefault()
|
||||
// #endif
|
||||
|
||||
if (evt.changedTouches.length == 0) return;
|
||||
move_x.value = evt.changedTouches[0].clientX
|
||||
move_y.value = evt.changedTouches[0].clientY
|
||||
}
|
||||
|
||||
function mEnd(evt : UniTouchEvent) {
|
||||
if (props.swiperLenClose == 0) return;
|
||||
if (evt.changedTouches.length == 0) return;
|
||||
let x = evt.changedTouches[0].clientX
|
||||
let y = evt.changedTouches[0].clientY
|
||||
move_end_x.value = x
|
||||
move_end_y.value = y;
|
||||
swiperClose();
|
||||
}
|
||||
|
||||
|
||||
function onScroll(evt : UniScrollEvent) {
|
||||
if (props.position == 'bottom') {
|
||||
if (evt.detail.scrollTop > 0) {
|
||||
start_move_x.value = move_x.value
|
||||
start_move_y.value = move_y.value
|
||||
}
|
||||
}
|
||||
if (props.position == 'top') {
|
||||
let ele = proxy!.$refs['xDrawerContent'] as UniElement | null;
|
||||
if (ele == null) return;
|
||||
let height = ele.getBoundingClientRect().height;
|
||||
let maxheight = evt.detail.scrollHeight - evt.detail.scrollTop
|
||||
if (evt.detail.scrollTop < maxheight - 1) {
|
||||
start_move_x.value = move_x.value
|
||||
start_move_y.value = move_y.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onScrollTop(evt : UniScrollToUpperEvent) {
|
||||
start_move_x.value = move_x.value
|
||||
start_move_y.value = move_y.value
|
||||
}
|
||||
|
||||
function onScrollBottom(evt : UniScrollToLowerEvent) {
|
||||
start_move_x.value = move_x.value
|
||||
start_move_y.value = move_y.value
|
||||
}
|
||||
|
||||
// 监听器
|
||||
watch(():boolean => props.show, (newval : boolean) => {
|
||||
if (newval) {
|
||||
showAlert()
|
||||
} else {
|
||||
closeAlert()
|
||||
}
|
||||
})
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
lezyShowModal.value = _lazy.value ? false : true;
|
||||
// #ifdef H5
|
||||
nextTick(() => {
|
||||
teleportTarget.value = getTeleportTarget()
|
||||
})
|
||||
// #endif
|
||||
|
||||
function oninitready(){
|
||||
isOpenedDefault.value = true;
|
||||
let sys = uni.getWindowInfo()
|
||||
// #ifdef WEB
|
||||
_width.value = sys.windowWidth
|
||||
_height.value = sys.windowHeight;
|
||||
windtop.value = sys.windowTop + _offset.value;
|
||||
// #endif
|
||||
// #ifdef APP|| MP-WEIXIN
|
||||
_width.value = sys.windowWidth
|
||||
_height.value = sys.windowHeight + 44;
|
||||
windtop.value = _offset.value;
|
||||
// #endif
|
||||
safeFooterHeight.value = sys.safeAreaInsets.bottom == 0 ? 16 : sys.safeAreaInsets.bottom
|
||||
if (_show.value) {
|
||||
showAlert();
|
||||
}
|
||||
}
|
||||
|
||||
oninitready()
|
||||
})
|
||||
|
||||
// 监听页面变化,重新获取teleport目标
|
||||
onUpdated(() => {
|
||||
// #ifdef H5
|
||||
if (teleportTarget.value && !isTeleportTargetValid(teleportTarget.value)) {
|
||||
teleportTarget.value = getTeleportTarget()
|
||||
}
|
||||
// #endif
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearTimeout(tid.value)
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
/** 打开 **/
|
||||
open:()=>showAlert(),
|
||||
/** 关闭 **/
|
||||
close:()=>closeAlert()
|
||||
})
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<view>
|
||||
<view @click="openDrawer">
|
||||
<!--
|
||||
@slot 标签触发显示遮罩,免于使用变量控制
|
||||
@prop {Boolean} show - 当前是否已显示
|
||||
-->
|
||||
<slot name="trigger" :show="show"></slot>
|
||||
</view>
|
||||
<!-- #ifdef H5 -->
|
||||
<teleport :to="teleportTarget || teleportElH5" :disabled="!teleportTarget">
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<root-portal >
|
||||
<!-- #endif -->
|
||||
|
||||
<view @click="onClickOverflowy" @touchmove="maskerMove" v-if="showOverflay" :id="id" ref="xDrawerWrap"
|
||||
class="xDrawerWrap" :class="[
|
||||
(_position=='top'||_position=='bottom')&&_widthCoverCenter?'xDrawerWrapContentMinwidthWrapDir':'',
|
||||
'xDrawerWrap_'+_position
|
||||
]" :style="[{backgroundColor:overflayBgColor,width:'100%',
|
||||
top:windtop+'px',
|
||||
height:__height,
|
||||
zIndex:zIndex,
|
||||
'transition-timing-function':_animationFun},_customStyle]">
|
||||
<view v-if="showOverflay&&_position == 'bottom'&&!actioning">
|
||||
<!--
|
||||
@slot 内容顶部的额外插槽,仅为下向上弹出(position=bottom)下才会显示。
|
||||
-->
|
||||
<slot name="contentTop"></slot>
|
||||
</view>
|
||||
<!-- @transitionend="onEnd" -->
|
||||
<view @click.stop="" ref="xDrawerWrapContent" class="xDrawerWrapContent"
|
||||
@touchstart="mStart" @touchmove="mMove" @touchend="mEnd" :class="[
|
||||
(_position=='top'||_position=='bottom')&&_widthCoverCenter?'xDrawerWrapContentMinwidth':'',
|
||||
'xDrawerWrapContent_'+_position
|
||||
]" :id="wrapId"
|
||||
:style="[
|
||||
{
|
||||
width:_position=='left'||_position=='right'?_size:'100%',
|
||||
height:_position=='left'||_position=='right'?'100%':_size,
|
||||
borderRadius:_round,
|
||||
maxHeight:_maxHeight!=''?_maxHeight:'100%',
|
||||
'transition-timing-function':_animationFun,
|
||||
backgroundColor:_bgColor,
|
||||
},
|
||||
customWrapStyle
|
||||
]">
|
||||
<view v-if="_showClose" class="xDrawerXclose">
|
||||
<x-icon @click="cancelEvt" :color="closeColor" :dark-color="closeDarkColor" font-size="24px"
|
||||
:name="_closeIcon"></x-icon>
|
||||
</view>
|
||||
|
||||
<view v-if="_showTitle">
|
||||
<!--
|
||||
@slot 标题插槽
|
||||
@prop {Boolean} show - 当前是否已显示
|
||||
-->
|
||||
<slot name="title" :show="show">
|
||||
<view class="xDrawerTitleBox">
|
||||
<text :style="{fontSize:_titleFontSize,color:_isDark?'white':'black',opacity:'0.64'}"
|
||||
class="xDrawertitleBox">{{_title}}</text>
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
|
||||
<x-loading v-if="!lezyShowModal"><text></text></x-loading>
|
||||
<view ref="xDrawerContent" class="xDrawerContent"
|
||||
:style="{flex:'1',margin:`0px 0px ${_contentMargin} 0px`}">
|
||||
<scroll-view v-if="!disabledScroll&&lezyShowModal" @scroll="onScroll"
|
||||
@scrolltoupper="onScrollTop" @scrolltolower="onScrollBottom"
|
||||
|
||||
<!-- #ifdef APP||WEB -->
|
||||
:style="{flex:'1'}"
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP -->
|
||||
:style="{position:'absolute',width:'100%',height:'100%'}"
|
||||
<!-- #endif -->
|
||||
|
||||
:scroll-y="true" :rebound="false">
|
||||
<view :style="{padding:`0px ${_contentMargin} 0px ${_contentMargin}`}">
|
||||
|
||||
<!--
|
||||
@slot 默认插槽
|
||||
-->
|
||||
<slot name="default"></slot>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view v-if="disabledScroll&&lezyShowModal"
|
||||
:style="{flex:'1',padding:`0px ${_contentMargin} 0px ${_contentMargin}`}">
|
||||
<!--
|
||||
默认插槽
|
||||
-->
|
||||
<slot name="default"></slot>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
<view v-if="showFooter&&lezyShowModal" class="xDrawerFooter" :style="{backgroundColor:_bgColor}">
|
||||
<!--
|
||||
@slot 底部操作栏
|
||||
-->
|
||||
<slot name="footer">
|
||||
<view style="flex-direction: row;align-items: center;justify-content: center;display: flex;">
|
||||
<x-button :disabled="isLoading" :color="_btnColor" @click="cancelEvt" v-if="_showCancel" skin="thin" width="0px" :block="true"
|
||||
style="margin-right: 16rpx;flex:1">{{_cancelText}}</x-button>
|
||||
<x-button :loading="isLoading" :color="_btnColor" @click="confirmEvt" width="0px" :disabled="_disabledConfirm" :block="true"
|
||||
style="flex:1">{{_confirmText}}</x-button>
|
||||
</view>
|
||||
</slot>
|
||||
<view :style="{height:safeFooterHeight+'px'}"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
</root-portal>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 -->
|
||||
</teleport>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
<style>
|
||||
.xDrawerContent{
|
||||
position: relative;
|
||||
|
||||
}
|
||||
.xDrawerFooter {
|
||||
width: 100%;
|
||||
/* background-color: white; */
|
||||
padding: 0 16px 0px 16px;
|
||||
/* #ifdef MP-WEIXIN||WEB */
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.xDrawerXclose {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 9px;
|
||||
z-index: 100;
|
||||
}
|
||||
.xDrawerXcloseOutter {
|
||||
padding-right: 16px;
|
||||
}
|
||||
.xDrawerTitleBox {
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.xDrawertitleBox {
|
||||
max-width: 175px;
|
||||
overflow: hidden;
|
||||
lines: 1;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
}
|
||||
|
||||
.xDrawerWrap_bottom {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.xDrawerWrap_top {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.xDrawerWrap_left {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.xDrawerWrap_right {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.xDrawerWrapContent {
|
||||
transition-duration: 350ms;
|
||||
transition-property: transform;
|
||||
/* background-color: white; */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
}
|
||||
|
||||
.xDrawerWrapContentMinwidth {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.xDrawerWrapContentMinwidthWrapDir {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.xDrawerWrapContent_bottom {
|
||||
transform: translate(0%, 100%);
|
||||
}
|
||||
|
||||
.xDrawerWrapContent_top {
|
||||
transform: translate(0%, -100%);
|
||||
}
|
||||
|
||||
.xDrawerWrapContent_left {
|
||||
transform: translate(-100%, 0%);
|
||||
}
|
||||
|
||||
.xDrawerWrapContent_right {
|
||||
transform: translate(100%, 0%);
|
||||
}
|
||||
|
||||
.xDrawerWrap {
|
||||
/* background: rgba(0, 0, 0, 0.4); */
|
||||
opacity: 0;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0px;
|
||||
/* #ifdef APP-HARMONY */
|
||||
transition-duration: 0ms;
|
||||
/* #endif */
|
||||
/* #ifndef APP-HARMONY */
|
||||
transition-duration: 350ms;
|
||||
/* #endif */
|
||||
transition-property: opacity;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user