perf: 优化抽屉窗口

This commit is contained in:
kuaifan 2025-08-07 14:22:00 +08:00
parent 710609e98b
commit d5a75f887d
4 changed files with 273 additions and 214 deletions

View File

@ -3,24 +3,28 @@
ref="modal"
v-model="show"
:closable="escClosable"
:mask="!isFullscreen"
:mask="finalMask"
:mask-closable="maskClosable"
:footer-hide="true"
:transition-names="transitionNames"
:beforeClose="beforeClose"
:class-name="className"
fullscreen>
<div v-if="isFullscreen" class="overlay-body">
<slot/>
</div>
:fullscreen="true"
:class-name="finalClassName"
:transition-names="finalTransitionNames"
:before-close="beforeClose">
<DrawerOverlayView
v-else
:placement="placementName"
:size="size"
:minSize="minSize"
:resize="resize"
:placement="finalPlacement"
:size="finalSize"
:minSize="finalMinSize"
:resize="finalResize"
@on-close="close">
<slot/>
<template v-if="$slots.title" #title>
<slot name="title"></slot>
</template>
<template v-if="$slots.more" #more>
<slot name="more"></slot>
</template>
<template #default>
<slot></slot>
</template>
</DrawerOverlayView>
</Modal>
</template>
@ -36,46 +40,55 @@ export default {
type: Boolean,
default: false
},
// 是否显示遮罩,留空自动判断
mask: {
default: null
},
// 允许点击遮罩关闭
maskClosable: {
type: Boolean,
default: true
},
// 允许按下 ESC 键关闭
escClosable: {
type: Boolean,
default: true
},
// 是否全屏,留空自动判断屏幕宽度小于 768px 时自动变为全屏模式
fullscreen: {
default: null
},
// 抽屉放置位置,可选 'right' 或 'bottom'
// 在全屏模式下无效,强制为 'bottom'
placement: {
validator(value) {
return ['right', 'bottom'].includes(value)
},
default: 'bottom'
},
forceFullscreen: {
type: Boolean,
default: false
},
transitions: {
type: Array,
default: () => []
},
// 抽屉的大小,可以是百分比或像素值,默认 100%
// 在全屏模式下无效
size: {
type: [Number, String],
default: "100%"
},
// 抽屉的最小大小,单位为像素,默认 300px
// 在全屏模式下无效
minSize: {
type: Number,
default: 300
},
// 允许调整大小,默认为 true
// 在全屏模式下无效
resize: {
type: Boolean,
default: true
},
drawerClass: {
type: String
},
modalClass: {
// 自定义类名
className: {
type: String
},
// 拦截关闭事件的函数
beforeClose: Function
},
data() {
@ -92,34 +105,55 @@ export default {
},
},
computed: {
isFullscreen() {
return this.forceFullscreen || (this.windowWidth < 500 && this.placement != 'bottom')
},
placementName() {
return this.isFullscreen ? 'bottom' : this.placement
},
transitionNames() {
if (this.transitions.length > 0) {
return this.transitions
finalFullscreen() {
if (typeof this.fullscreen === 'boolean') {
return this.fullscreen
}
return [`drawer-slide-${this.placementName}`, '']
return this.windowWidth < 768
},
className() {
const array = []
if (this.isFullscreen) {
array.push("common-drawer-modal")
if (this.modalClass) {
array.push(this.modalClass)
}
} else {
array.push("common-drawer-overlay")
if (this.drawerClass) {
array.push(this.drawerClass)
}
array.push(this.placementName)
finalMask() {
if (typeof this.mask === 'boolean') {
return this.mask
}
return !this.finalFullscreen
},
finalClassName() {
const array = [
"common-drawer",
`drawer-${this.finalPlacement}`
];
if (this.finalFullscreen) {
array.push("drawer-fullscreen")
}
if (this.className) {
array.push(this.className)
}
return array.join(" ");
},
finalTransitionNames() {
return [`drawer-animation-${this.finalPlacement}`, 'drawer-animation-fade']
},
finalPlacement() {
return this.finalFullscreen ? 'bottom' : this.placement
},
finalSize() {
if (this.finalFullscreen) {
return "100%"
}
return this.size
},
finalMinSize() {
if (this.finalFullscreen) {
return 0
}
return this.minSize
},
finalResize() {
if (this.finalFullscreen) {
return false
}
return this.resize
}
},
methods: {
close() {

View File

@ -1,11 +1,9 @@
<template>
<div ref="body" class="overlay-body" :style="bodyStyle">
<div class="overlay-close">
<a href="javascript:void(0)" @click.stop="onClose">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26" fill="none" role="img" class="icon fill-current">
<path d="M8.28596 6.51819C7.7978 6.03003 7.00634 6.03003 6.51819 6.51819C6.03003 7.00634 6.03003 7.7978 6.51819 8.28596L11.2322 13L6.51819 17.714C6.03003 18.2022 6.03003 18.9937 6.51819 19.4818C7.00634 19.97 7.7978 19.97 8.28596 19.4818L13 14.7678L17.714 19.4818C18.2022 19.97 18.9937 19.97 19.4818 19.4818C19.97 18.9937 19.97 18.2022 19.4818 17.714L14.7678 13L19.4818 8.28596C19.97 7.7978 19.97 7.00634 19.4818 6.51819C18.9937 6.03003 18.2022 6.03003 17.714 6.51819L13 11.2322L8.28596 6.51819Z" fill="currentColor"></path>
</svg>
</a>
<div class="overlay-close" @click.stop="onClose">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26">
<path d="M8.28596 6.51819C7.7978 6.03003 7.00634 6.03003 6.51819 6.51819C6.03003 7.00634 6.03003 7.7978 6.51819 8.28596L11.2322 13L6.51819 17.714C6.03003 18.2022 6.03003 18.9937 6.51819 19.4818C7.00634 19.97 7.7978 19.97 8.28596 19.4818L13 14.7678L17.714 19.4818C18.2022 19.97 18.9937 19.97 19.4818 19.4818C19.97 18.9937 19.97 18.2022 19.4818 17.714L14.7678 13L19.4818 8.28596C19.97 7.7978 19.97 7.00634 19.4818 6.51819C18.9937 6.03003 18.2022 6.03003 17.714 6.51819L13 11.2322L8.28596 6.51819Z" fill="currentColor"></path>
</svg>
</div>
<ResizeLine
v-if="resize"
@ -19,6 +17,14 @@
@on-change="onChangeResize"/>
<div class="overlay-content">
<div class="overlay-content-status"></div>
<div v-if="$slots.title || $slots.more" class="overlay-content-header">
<div class="overlay-content-header-title">
<slot name="title"/>
</div>
<div class="overlay-content-header-more">
<slot name="more"/>
</div>
</div>
<div class="overlay-content-body"><slot/></div>
<div class="overlay-content-navigation"></div>
</div>

View File

@ -1,36 +1,30 @@
body {
.ivu-modal-wrap {
&.common-drawer-overlay {
.ivu-modal {
.ivu-modal-content {
margin-top: 0;
margin-bottom: 0;
}
}
}
.common-drawer {
--margin-top: 8px;
--margin-left: 8px;
--margin-right: 8px;
--margin-bottom: 8px;
--close-top: 16px;
--close-right: 16px;
--close-size: 40px;
--close-color: #606266;
--title-color: #303133;
--content-bg-color: #ffffff;
--border-radius: 16px;
&.file-drawer {
--margin-top: 40px;
--margin-left: 0px;
--margin-right: 0px;
--margin-bottom: 0px;
--close-top: 0px;
--close-right: 0px;
--close-color: #ffffff;
--border-radius: 16px 16px 0 0;
}
}
.common-drawer-overlay {
.ivu-modal {
margin: 0;
padding: 0;
.ivu-modal-content {
background: transparent;
.ivu-modal-close {
display: none;
}
.ivu-modal-body {
padding: 0;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-content: flex-end;
}
}
&.approve-drawer {
--close-top: 28px;
--close-right: 20px;
}
.overlay-body {
@ -41,154 +35,204 @@ body {
max-width: 100%;
max-height: 100%;
position: relative;
z-index: 2;
.overlay-close {
flex-shrink: 0;
position: absolute;
z-index: 2;
top: var(--close-top);
right: var(--close-right);
margin-top: var(--status-bar-height);
width: var(--close-size);
height: var(--close-size);
display: flex;
align-items: flex-end;
justify-content: flex-end;
padding-top: var(--status-bar-height);
justify-content: center;
align-items: center;
cursor: pointer;
> a {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
color: #fff;
.icon {
width: 24px;
height: 24px
}
> svg {
color: var(--close-color);
opacity: 0.8;
width: 24px;
height: 24px;
transition: all 0.3s;
}
&:hover {
> svg {
transition: all 0.3s;
}
&:hover {
color: #fff;
> svg {
transform: rotate(-90deg);
}
opacity: 1;
transform: rotate(-90deg);
}
}
}
.overlay-resize {
width: 100%;
height: 5px;
margin-bottom: -5px;
z-index: 1;
position: absolute;
top: 0;
left: var(--margin-left);
right: auto;
bottom: 0;
width: 5px;
z-index: 2;
&.bottom {
left: 0;
right: 0;
top: var(--margin-top);
bottom: 0;
width: 100%;
height: 5px;
}
}
.overlay-content {
flex: 1;
min-height: 0;
position: relative;
background: #fff;
border-radius: 18px 18px 0 0;
z-index: 1;
background-color: var(--content-bg-color);
margin: var(--margin-top) var(--margin-right) var(--margin-bottom) var(--margin-left);
border-radius: var(--border-radius);
overflow: hidden;
cursor: default;
display: flex;
flex-direction: column;
.overlay-content-status {
&-status {
flex-shrink: 0;
height: var(--status-bar-height);
}
.overlay-content-body {
&-header {
flex-shrink: 0;
padding: calc(var(--close-top) - var(--margin-top)) calc(var(--close-size) + var(--close-right)) calc(var(--close-top) - var(--margin-top)) 30px;
box-sizing: content-box;
display: flex;
align-items: center;
justify-content: space-between;
min-height: 40px;
gap: 12px;
&-title {
flex: 1;
min-width: 0;
color: var(--title-color);
font-size: 20px;
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&-more {
flex-shrink: 0;
}
}
&-body {
flex: 1;
height: 0;
min-height: 0;
position: relative;
}
.overlay-content-navigation {
&-navigation {
flex-shrink: 0;
height: var(--navigation-bar-height);
}
.ivu-modal-wrap-apply {
.ivu-modal-wrap-apply-title {
border-top-left-radius: 18px;
}
}
}
}
}
.drawer-fullscreen {
--margin-top: 0px;
--margin-left: 0px;
--margin-right: 0px;
--margin-bottom: 0px;
--close-top: 8px;
--close-right: 8px;
--border-radius: 0;
&.approve-drawer {
--close-top: 20px;
--close-right: 12px;
}
}
body {
.ivu-modal-wrap {
&.common-drawer {
overflow: hidden;
.ivu-modal {
.ivu-modal-content {
margin-top: 0;
margin-bottom: 0;
background-color: transparent;
}
.ivu-modal-close {
display: none;
}
&.right {
.ivu-modal {
.ivu-modal-content {
.ivu-modal-body {
flex-direction: row;
padding: 0;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: flex-end;
}
}
}
}
}
.overlay-body {
flex-direction: row;
.overlay-close {
align-items: flex-start;
}
.overlay-resize {
width: 5px;
height: 100%;
margin-right: -5px;
z-index: 1;
}
.overlay-content {
border-radius: 18px 0 0 18px;
}
body.dark-mode-reverse {
.common-drawer {
&.file-drawer {
--close-color: #000000;
}
}
}
.drawer-animation {
&-fade {
&-enter-active,
&-leave-active {
transition: opacity .5s cubic-bezier(0.32, 0.72, 0, 1);
}
.common-drawer-modal {
.ivu-modal-fullscreen {
background-color: #ffffff;
&-enter,
&-leave-to {
opacity: 0;
}
}
.overlay-body {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
max-width: 100%;
max-height: 100%;
position: relative;
z-index: 2;
&-right {
&-enter-active,
&-leave-active {
touch-action: none;
will-change: transform, opacity;
transition: transform .2s cubic-bezier(0.32, 0.72, 0, 1), opacity .2s cubic-bezier(0.32, 0.72, 0, 1);
}
&-enter,
&-leave-to {
transform: translate(15%, 0) scale(0.98);
opacity: 0;
}
}
&-bottom {
&-enter-active,
&-leave-active {
touch-action: none;
will-change: transform, opacity;
transition: transform .2s cubic-bezier(0.32, 0.72, 0, 1), opacity .2s cubic-bezier(0.32, 0.72, 0, 1);
}
&-enter,
&-leave-to {
transform: translate(0, 15%) scale(0.98);
opacity: 0;
}
}
}
.drawer-slide-bottom-enter-active {
transition: all .2s ease;
}
.drawer-slide-bottom-leave-active {
transition: all .2s ease;
}
.drawer-slide-bottom-enter,
.drawer-slide-bottom-leave-to {
transform: translate(0, 15%) scale(0.98);
opacity: 0;
}
.drawer-slide-right-enter-active {
transition: all .2s ease;
}
.drawer-slide-right-leave-active {
transition: all .2s ease;
}
.drawer-slide-right-enter,
.drawer-slide-right-leave-to {
transform: translate(15%, 0) scale(0.98);
opacity: 0;
}

View File

@ -824,31 +824,6 @@ body {
opacity: 0;
}
/*全局 ivu-modal-wrap*/
body {
.ivu-modal-wrap {
&.common-drawer-overlay {
overflow: hidden;
}
&.common-drawer-modal {
.ivu-modal-body {
padding: 0 !important;
.dialog-wrapper {
&.inde-list {
border-radius: 0
}
}
}
.ivu-modal-close {
z-index: 3;
}
}
}
}
/*全局返回按钮*/
.common-nav-back{
cursor: pointer;