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 ''
} }
},
subtitle: { withDefaults(defineProps<Props>(), {
type: String, title: '',
default() { subtitle: '',
return '' subtitleColor: 'gray',
} okText: '确定',
}, cancelText: '取消',
subtitleColor: { cancelTextColor: 'gray'
type: String, })
default() {
return 'gray' const emit = defineEmits<{
} (ev: 'ok'): void
}, (ev: 'cancel'): void
okText: { (ev: 'dismiss'): void
type: String, }>()
default() {
return '确定' const visible = defineModel<boolean>('visible', { type: Boolean, default: true })
}
}, const onOk = () => {
cancelText: { visible.value = false
type: String, emit('ok')
default() {
return '取消'
}
},
cancelTextColor: {
type: String,
default() {
return 'gray'
}
}
},
data() {
return {}
},
methods: {
stop() {},
ok() {
this.$emit('ok')
this.$emit('update:visible', false)
},
cancel() {
this.$emit('cancel')
this.$emit('update:visible', false)
} }
const onCancel = () => {
visible.value = false
emit('cancel')
} }
const onDismiss = () => {
emit('dismiss')
} }
</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,74 +18,82 @@
</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: { interface Props {
modelValue: { mode?: 'dark' | 'light' | 'white'
type: Boolean, maskMode?: 'dark' | 'light' | 'white'
default: false height?: string
}, showHengGang?: boolean
mode: { pageId: string | null
type: String, borderRadius?: string
default: 'dark' tag?: string
// 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: { interface Emits {
modelValue(newVal) { (ev: 'cancel'): void
let page = document.getElementById(this.pageId) }
const props = withDefaults(defineProps<Props>(), {
mode: 'dark',
maskMode: 'dark',
height: 'calc(var(--vh, 1vh) * 70)',
showHengGang: true,
pageId: null,
borderRadius: '15rem 15rem 0 0',
tag: ''
})
const emit = defineEmits<Emits>()
const modelValue = defineModel<boolean>('value', { type: Boolean, default: false })
const dialog = ref<HTMLElement | null>(null)
const wrapper = ref<HTMLElement | null>(null)
const scroll = ref(0)
const startY = ref(0)
const moveY = ref(0)
const startTime = ref(0)
const pagePosition = ref(null)
watch(
() => modelValue.value,
(newVal: boolean) => {
let page = document.getElementById(props.pageId)
if (!page) return if (!page) return
if (newVal) { if (newVal) {
this.pagePosition = _css(page, 'position') pagePosition.value = _css(page, 'position')
page.style.position = 'absolute' page.style.position = 'absolute'
this.scroll = document.documentElement.scrollTop scroll.value = document.documentElement.scrollTop
document.body.style.position = 'fixed' document.body.style.position = 'fixed'
document.body.style.top = -this.scroll + 'px' document.body.style.top = -scroll.value + 'px'
let maskTemplate = `<div class="Mask fade-in ${this.maskMode}"></div>` let maskTemplate = `<div class="Mask fade-in ${props.maskMode}"></div>`
let mask = new Dom().create(maskTemplate) let mask = new Dom().create(maskTemplate)
setTimeout(() => { setTimeout(() => {
mask.on('click', (e) => { mask.on('click', (e: Event) => {
_stopPropagation(e) _stopPropagation(e)
this.hide(false) onHide()
}) })
}, 200) }, 200)
page.appendChild(mask.els[0]) page.appendChild(mask.els[0])
} else { } else {
page.style.position = this.pagePosition || 'fixed' page.style.position = pagePosition.value || 'fixed'
document.body.style.position = 'static' document.body.style.position = 'static'
document.documentElement.scrollTop = this.scroll document.documentElement.scrollTop = scroll.value
let mask = new Dom('.Mask').replaceClass('fade-in', 'fade-out') let mask = new Dom('.Mask').replaceClass('fade-in', 'fade-out')
setTimeout(() => { setTimeout(() => {
@ -95,55 +101,45 @@ export default {
}, 250) }, 250)
} }
} }
}, )
data() {
return { const onHide = () => {
scroll: 0, modelValue.value = false
startY: 0, emit('cancel')
moveY: 0,
startTime: 0,
pagePosition: null
} }
},
computed: {}, const onStart = (e: TouchEvent) => {
created() {}, if (wrapper.value?.scrollTop !== 0) return
methods: { startY.value = e.touches[0].clientY
hide(val = false) { startTime.value = Date.now()
this.$emit('update:modelValue', val) _css(dialog.value, 'transition-duration', '0ms')
this.$emit('cancel') }
},
start(e) { const onMove = (e: TouchEvent) => {
if (this.$refs.wrapper.scrollTop !== 0) return if (wrapper.value?.scrollTop !== 0) return
this.startY = e.touches[0].pageY moveY.value = e.touches[0].pageY - startY.value
this.startTime = Date.now() if (moveY.value < 0) {
_css(this.$refs.dialog, 'transition-duration', `0ms`)
},
move(e) {
if (this.$refs.wrapper.scrollTop !== 0) return
this.moveY = e.touches[0].pageY - this.startY
if (this.moveY > 0) {
bus.emit(EVENT_KEY.DIALOG_MOVE, { bus.emit(EVENT_KEY.DIALOG_MOVE, {
tag: this.tag, tag: props.tag,
e: this.moveY e: moveY.value
}) })
_css(this.$refs.dialog, 'transform', `translate3d(0,${this.moveY}px,0)`) _css(dialog.value, 'transform', `translate3d(0, ${moveY.value}px, 0)`)
} }
}, }
end() {
const onEnd = () => {
// modelValue ref // modelValue ref
if (!this.$refs.dialog) return if (!dialog.value) return
if (Date.now() - this.startTime < 150 && Math.abs(this.moveY) < 30) return if (Date.now() - startTime.value < 150 && Math.abs(moveY.value) < 30) return
let clientHeight = this.$refs.dialog.clientHeight let clientHeight = dialog.value?.clientHeight
_css(this.$refs.dialog, 'transition-duration', `250ms`) _css(dialog.value, 'transition-duration', `250ms`)
if (Math.abs(this.moveY) > clientHeight / 2) { if (Math.abs(moveY.value) > clientHeight / 2) {
_css(this.$refs.dialog, 'transform', `translate3d(0,100%,0)`) _css(dialog.value, 'transform', `translate3d(0,100%,0)`)
bus.emit(EVENT_KEY.DIALOG_END, { tag: this.tag, isClose: true }) bus.emit(EVENT_KEY.DIALOG_END, { tag: props.tag, isClose: true })
setTimeout(this.hide, 250) setTimeout(onHide, 250)
} else { } else {
_css(this.$refs.dialog, 'transform', `translate3d(0,0,0)`) _css(dialog.value, 'transform', `translate3d(0,0,0)`)
bus.emit(EVENT_KEY.DIALOG_END, { tag: this.tag, isClose: false }) 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 ''
} }
},
subtitle: { withDefaults(defineProps<Props>(), {
type: String, title: '',
default() { subtitle: '',
return '' subtitleColor: 'gray',
} cancelText: '取消'
}, })
subtitleColor: {
type: String, const emit = defineEmits<{
default() { (ev: 'ok'): void
return 'gray' (ev: 'cancel'): void
} (ev: 'dismiss'): void
}, }>()
cancelText: {
type: String, const visible = defineModel<boolean>('visible', { type: Boolean, default: true })
default() {
return '取消' const onCancel = () => {
} visible.value = false
} emit('cancel')
},
data() {
return {}
},
methods: {
stop() {}
} }
const onDismiss = () => {
emit('dismiss')
} }
</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: () => []
})
const emit = defineEmits<{
(ev: 'ok', item: Item): void
(ev: 'cancel'): void
}>()
const onOk = (item: Item) => {
emit('ok', item)
} }
},
data() { const onCancel = () => {
return {} 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() {
return ''
} }
},
okText: { withDefaults(defineProps<Props>(), {
type: String, title: '',
default() { okText: '确定',
return '保存' cancelText: '取消'
})
const emit = defineEmits<{
(ev: 'ok'): void
(ev: 'cancel'): void
(ev: 'dismiss'): void
}>()
const visible = defineModel<boolean>('visible', { type: Boolean, default: true })
const onOk = () => {
visible.value = false
emit('ok')
} }
},
cancelText: { const onCancel = () => {
type: String, visible.value = false
default() { emit('cancel')
return '放弃'
}
}
},
data() {
return {}
},
methods: {
stop() {}
} }
const onDismiss = () => {
emit('dismiss')
} }
</script> </script>