Merge pull request #66 from Chanzhaoyu/dialog-components

perf: 优化 dilog 下组件
This commit is contained in:
Zyronon 2024-04-29 14:15:57 +08:00 committed by GitHub
commit 9da5f780db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 275 additions and 298 deletions

View File

@ -1,83 +1,65 @@
<template> <template>
<div class="ConfirmDialog" @click="$emit('dismiss')" v-if="visible"> <div class="ConfirmDialog" @click="onDismiss" v-if="visible">
<div class="content" @click.stop="stop"> <div class="content">
<slot name="header"></slot> <slot name="header"></slot>
<div class="body"> <div class="body">
<div class="title" v-if="title">{{ title }}</div> <div class="title" v-if="title">{{ title }}</div>
<div class="subtitle" :class="subtitleColor" v-if="subtitle"> <div :class="['subtitle', subtitleColor]" v-if="subtitle">
{{ subtitle }} {{ subtitle }}
</div> </div>
<slot></slot> <slot></slot>
</div> </div>
<div class="footer"> <div class="footer">
<div class="cancel" :class="cancelTextColor" @click.stop="cancel"> <div :class="['cancel', cancelTextColor]" @click.stop="onCancel">
{{ cancelText }} {{ cancelText }}
</div> </div>
<div class="ok" @click.stop="ok">{{ okText }}</div> <div class="ok" @click.stop="onOk">{{ okText }}</div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts">
/*TODO 单独使用时没有mark*/ <script setup lang="ts">
export default { defineOptions({ name: 'ConfirmDialog' })
name: 'ConfirmDialog',
props: { interface Props {
visible: { title?: string
type: Boolean, subtitle?: string
default: true subtitleColor?: string
}, okText?: string
title: { cancelText?: string
type: String, cancelTextColor?: string
default() { }
return ''
} withDefaults(defineProps<Props>(), {
}, title: '',
subtitle: { subtitle: '',
type: String, subtitleColor: 'gray',
default() { okText: '确定',
return '' cancelText: '取消',
} cancelTextColor: 'gray'
}, })
subtitleColor: {
type: String, const emit = defineEmits<{
default() { (ev: 'ok'): void
return 'gray' (ev: 'cancel'): void
} (ev: 'dismiss'): void
}, }>()
okText: {
type: String, const visible = defineModel<boolean>('visible', { type: Boolean, default: true })
default() {
return '确定' const onOk = () => {
} visible.value = false
}, emit('ok')
cancelText: { }
type: String,
default() { const onCancel = () => {
return '取消' visible.value = false
} emit('cancel')
}, }
cancelTextColor: {
type: String, const onDismiss = () => {
default() { emit('dismiss')
return 'gray'
}
}
},
data() {
return {}
},
methods: {
stop() {},
ok() {
this.$emit('ok')
this.$emit('update:visible', false)
},
cancel() {
this.$emit('cancel')
this.$emit('update:visible', false)
}
}
} }
</script> </script>

View File

@ -3,16 +3,9 @@
<slot></slot> <slot></slot>
</div> </div>
</template> </template>
<script>
export default { <script setup lang="ts">
name: 'FadeDialog', defineOptions({ name: 'FadeDialog' })
data() {
return {}
},
computed: {},
created() {},
methods: {}
}
</script> </script>
<style scoped lang="less"> <style scoped lang="less">

View File

@ -1,17 +1,15 @@
<template> <template>
<!-- <transition> -->
<Transition name="test"> <Transition name="test">
<div <div
ref="dialog" ref="dialog"
class="FromBottomDialog"
v-if="modelValue" v-if="modelValue"
:class="[mode, showHengGang ? '' : 'no-heng-gang']" :class="['FromBottomDialog', mode, showHengGang ? '' : 'no-heng-gang']"
@touchstart="start" @touchstart="onStart"
@touchmove="move" @touchmove="onMove"
@touchend="end" @touchend="onEnd"
> >
<slot name="header"></slot> <slot name="header"></slot>
<div class="heng-gang" :class="mode" v-if="showHengGang"> <div :class="['heng-gang', mode]" v-if="showHengGang">
<div class="content"></div> <div class="content"></div>
</div> </div>
<div class="wrapper" ref="wrapper"> <div class="wrapper" ref="wrapper">
@ -20,131 +18,129 @@
</div> </div>
</Transition> </Transition>
</template> </template>
<script>
<script setup lang="ts">
import { ref, watch } from 'vue'
import Dom, { _css } from '../../utils/dom' import Dom, { _css } from '../../utils/dom'
import bus, { EVENT_KEY } from '@/utils/bus' import bus, { EVENT_KEY } from '@/utils/bus'
import { _stopPropagation } from '@/utils' import { _stopPropagation } from '@/utils'
export default { defineOptions({ name: 'FromBottomDialog' })
name: 'FromBottomDialog',
props: {
modelValue: {
type: Boolean,
default: false
},
mode: {
type: String,
default: 'dark'
// default: 'light'
// default: 'white'
},
maskMode: {
type: String,
default: 'dark'
},
height: {
type: String,
default: 'calc(var(--vh, 1vh) * 70)'
},
showHengGang: {
type: Boolean,
default: true
},
pageId: {
type: String,
default: null,
required: true
},
borderRadius: {
type: String,
default: '15rem 15rem 0 0'
},
tag: {
type: String,
default: ''
}
},
watch: {
modelValue(newVal) {
let page = document.getElementById(this.pageId)
if (!page) return
if (newVal) {
this.pagePosition = _css(page, 'position')
page.style.position = 'absolute'
this.scroll = document.documentElement.scrollTop
document.body.style.position = 'fixed'
document.body.style.top = -this.scroll + 'px'
let maskTemplate = `<div class="Mask fade-in ${this.maskMode}"></div>` interface Props {
let mask = new Dom().create(maskTemplate) mode?: 'dark' | 'light' | 'white'
setTimeout(() => { maskMode?: 'dark' | 'light' | 'white'
mask.on('click', (e) => { height?: string
_stopPropagation(e) showHengGang?: boolean
this.hide(false) pageId: string | null
}) borderRadius?: string
}, 200) tag?: string
page.appendChild(mask.els[0]) }
} else {
page.style.position = this.pagePosition || 'fixed'
document.body.style.position = 'static'
document.documentElement.scrollTop = this.scroll
let mask = new Dom('.Mask').replaceClass('fade-in', 'fade-out') interface Emits {
setTimeout(() => { (ev: 'cancel'): void
mask.remove() }
}, 250)
} const props = withDefaults(defineProps<Props>(), {
} mode: 'dark',
}, maskMode: 'dark',
data() { height: 'calc(var(--vh, 1vh) * 70)',
return { showHengGang: true,
scroll: 0, pageId: null,
startY: 0, borderRadius: '15rem 15rem 0 0',
moveY: 0, tag: ''
startTime: 0, })
pagePosition: null
} const emit = defineEmits<Emits>()
},
computed: {}, const modelValue = defineModel<boolean>('value', { type: Boolean, default: false })
created() {},
methods: { const dialog = ref<HTMLElement | null>(null)
hide(val = false) {
this.$emit('update:modelValue', val) const wrapper = ref<HTMLElement | null>(null)
this.$emit('cancel')
}, const scroll = ref(0)
start(e) {
if (this.$refs.wrapper.scrollTop !== 0) return const startY = ref(0)
this.startY = e.touches[0].pageY
this.startTime = Date.now() const moveY = ref(0)
_css(this.$refs.dialog, 'transition-duration', `0ms`)
}, const startTime = ref(0)
move(e) {
if (this.$refs.wrapper.scrollTop !== 0) return const pagePosition = ref(null)
this.moveY = e.touches[0].pageY - this.startY
if (this.moveY > 0) { watch(
bus.emit(EVENT_KEY.DIALOG_MOVE, { () => modelValue.value,
tag: this.tag, (newVal: boolean) => {
e: this.moveY let page = document.getElementById(props.pageId)
if (!page) return
if (newVal) {
pagePosition.value = _css(page, 'position')
page.style.position = 'absolute'
scroll.value = document.documentElement.scrollTop
document.body.style.position = 'fixed'
document.body.style.top = -scroll.value + 'px'
let maskTemplate = `<div class="Mask fade-in ${props.maskMode}"></div>`
let mask = new Dom().create(maskTemplate)
setTimeout(() => {
mask.on('click', (e: Event) => {
_stopPropagation(e)
onHide()
}) })
_css(this.$refs.dialog, 'transform', `translate3d(0,${this.moveY}px,0)`) }, 200)
} page.appendChild(mask.els[0])
}, } else {
end() { page.style.position = pagePosition.value || 'fixed'
//modelValueref document.body.style.position = 'static'
if (!this.$refs.dialog) return document.documentElement.scrollTop = scroll.value
if (Date.now() - this.startTime < 150 && Math.abs(this.moveY) < 30) return
let clientHeight = this.$refs.dialog.clientHeight let mask = new Dom('.Mask').replaceClass('fade-in', 'fade-out')
_css(this.$refs.dialog, 'transition-duration', `250ms`) setTimeout(() => {
if (Math.abs(this.moveY) > clientHeight / 2) { mask.remove()
_css(this.$refs.dialog, 'transform', `translate3d(0,100%,0)`) }, 250)
bus.emit(EVENT_KEY.DIALOG_END, { tag: this.tag, isClose: true })
setTimeout(this.hide, 250)
} else {
_css(this.$refs.dialog, 'transform', `translate3d(0,0,0)`)
bus.emit(EVENT_KEY.DIALOG_END, { tag: this.tag, isClose: false })
}
} }
} }
)
const onHide = () => {
modelValue.value = false
emit('cancel')
}
const onStart = (e: TouchEvent) => {
if (wrapper.value?.scrollTop !== 0) return
startY.value = e.touches[0].clientY
startTime.value = Date.now()
_css(dialog.value, 'transition-duration', '0ms')
}
const onMove = (e: TouchEvent) => {
if (wrapper.value?.scrollTop !== 0) return
moveY.value = e.touches[0].pageY - startY.value
if (moveY.value < 0) {
bus.emit(EVENT_KEY.DIALOG_MOVE, {
tag: props.tag,
e: moveY.value
})
_css(dialog.value, 'transform', `translate3d(0, ${moveY.value}px, 0)`)
}
}
const onEnd = () => {
// modelValue ref
if (!dialog.value) return
if (Date.now() - startTime.value < 150 && Math.abs(moveY.value) < 30) return
let clientHeight = dialog.value?.clientHeight
_css(dialog.value, 'transition-duration', `250ms`)
if (Math.abs(moveY.value) > clientHeight / 2) {
_css(dialog.value, 'transform', `translate3d(0,100%,0)`)
bus.emit(EVENT_KEY.DIALOG_END, { tag: props.tag, isClose: true })
setTimeout(onHide, 250)
} else {
_css(dialog.value, 'transform', `translate3d(0,0,0)`)
bus.emit(EVENT_KEY.DIALOG_END, { tag: props.tag, isClose: false })
}
} }
</script> </script>

View File

@ -1,57 +1,51 @@
<template> <template>
<div class="NoticeDialog" @click="$emit('dismiss')"> <div class="NoticeDialog" @click="onDismiss">
<div class="content" @click.stop="stop"> <div class="content">
<div class="body"> <div class="body">
<div class="title">{{ title }}</div> <div class="title">{{ title }}</div>
<div class="subtitle" :class="subtitleColor" v-if="subtitle"> <div :class="['subtitle', subtitleColor]" v-if="subtitle">
{{ subtitle }} {{ subtitle }}
</div> </div>
</div> </div>
<div class="footer"> <div class="footer">
<div class="cancel" @click.stop="$emit('cancel')">{{ cancelText }}</div> <div class="cancel" @click.stop="onCancel">{{ cancelText }}</div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script>
export default { <script setup lang="ts">
name: 'NoticeDialog', defineOptions({ name: 'NoticeDialog' })
props: {
visible: { interface Props {
type: Boolean, title?: string
default: false subtitle?: string
}, subtitleColor?: string
title: { cancelText?: string
type: String, }
default() {
return '' withDefaults(defineProps<Props>(), {
} title: '',
}, subtitle: '',
subtitle: { subtitleColor: 'gray',
type: String, cancelText: '取消'
default() { })
return ''
} const emit = defineEmits<{
}, (ev: 'ok'): void
subtitleColor: { (ev: 'cancel'): void
type: String, (ev: 'dismiss'): void
default() { }>()
return 'gray'
} const visible = defineModel<boolean>('visible', { type: Boolean, default: true })
},
cancelText: { const onCancel = () => {
type: String, visible.value = false
default() { emit('cancel')
return '取消' }
}
} const onDismiss = () => {
}, emit('dismiss')
data() {
return {}
},
methods: {
stop() {}
}
} }
</script> </script>

View File

@ -1,30 +1,39 @@
<template> <template>
<div class="SelectDialog" @click="$emit('cancel')"> <div class="SelectDialog" @click="onCancel">
<div class="content"> <div class="content">
<div class="item" :key="i" v-for="(item, i) in list" @click.stop="$emit('ok', item)"> <div class="item" :key="i" v-for="(item, i) in list" @click.stop="onOk(item)">
{{ item.name }} {{ item.name }}
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script>
export default { <script setup lang="ts" generic="T">
name: 'SelectDialog', defineOptions({ name: 'SelectDialog' })
props: {
visible: { type Item = { name: string } & T
type: Boolean,
default: false interface Props {
}, visible?: boolean
list: { list?: Item[]
type: Array, }
default() {
return [] withDefaults(defineProps<Props>(), {
} visible: false,
} list: () => []
}, })
data() {
return {} const emit = defineEmits<{
} (ev: 'ok', item: Item): void
(ev: 'cancel'): void
}>()
const onOk = (item: Item) => {
emit('ok', item)
}
const onCancel = () => {
emit('cancel')
} }
</script> </script>

View File

@ -1,47 +1,50 @@
<template> <template>
<div class="SimpleConfirmDialog" @click="$emit('dismiss')"> <div class="SimpleConfirmDialog" @click="onDismiss">
<div class="content" @click.stop="stop"> <div class="content">
<div class="item">{{ title }}</div> <div class="item">{{ title }}</div>
<div class="footer"> <div class="footer">
<div class="cancel" @click.stop="$emit('cancel')">{{ cancelText }}</div> <div class="cancel" @click.stop="onCancel">{{ cancelText }}</div>
<div class="ok" @click.stop="$emit('ok')">{{ okText }}</div> <div class="ok" @click.stop="onOk">{{ okText }}</div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script>
export default { <script setup lang="ts">
name: 'SimpleConfirmDialog', defineOptions({ name: 'SimpleConfirmDialog' })
props: {
visible: { interface Props {
type: Boolean, title?: string
default: false okText?: string
}, cancelText?: string
title: { }
type: String,
default() { withDefaults(defineProps<Props>(), {
return '' title: '',
} okText: '确定',
}, cancelText: '取消'
okText: { })
type: String,
default() { const emit = defineEmits<{
return '保存' (ev: 'ok'): void
} (ev: 'cancel'): void
}, (ev: 'dismiss'): void
cancelText: { }>()
type: String,
default() { const visible = defineModel<boolean>('visible', { type: Boolean, default: true })
return '放弃'
} const onOk = () => {
} visible.value = false
}, emit('ok')
data() { }
return {}
}, const onCancel = () => {
methods: { visible.value = false
stop() {} emit('cancel')
} }
const onDismiss = () => {
emit('dismiss')
} }
</script> </script>