Files

263 lines
6.1 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="center-modal" v-if="visible">
<!-- 遮罩层点击关闭mask dialog-wrap 是兄弟节点点击不会相互干扰 -->
<view class="cm-mask" @click="handleMaskClick" hover-stop-propagation></view>
<!-- 弹窗容器绝对定位居中 -->
<view class="cm-dialog-wrap">
<!-- 弹窗主体 -->
<view class="cm-dialog" :style="{ backgroundColor: themeConfig.cardBgColor }">
<view class="cm-header" v-if="title || $slots.header">
<slot name="header">
<text class="cm-title" :style="{ color: themeConfig.textPrimary }">{{ title }}</text>
</slot>
</view>
<view class="cm-body" :style="{ maxHeight: maxBodyHeight }">
<slot></slot>
</view>
<view class="cm-footer" v-if="showCancel || showConfirm || $slots.footer">
<slot name="footer">
<view
v-if="showCancel"
class="cm-btn cm-btn--cancel"
:style="{ background: cancelBg, color: themeConfig.textPrimary }"
hover-class="cm-btn--cancel--active"
:hover-stay-time="100"
@click="handleCancel"
>
<text class="cm-btn__text">{{ cancelText }}</text>
</view>
<view
v-if="showConfirm"
class="cm-btn cm-btn--confirm"
:class="{ 'cm-btn--disabled': confirmDisabled }"
:style="confirmBtnStyle"
hover-class="cm-btn--confirm--active"
:hover-stay-time="100"
@click="handleConfirm"
>
<text class="cm-btn__text">{{ confirmText }}</text>
</view>
</slot>
</view>
</view>
<!-- 关闭按钮放在弹窗容器内 dialog 平级避免被 dialog 拦截事件 -->
<view
v-if="showClose"
class="cm-close"
:style="{ background: themeConfig.cardBgColor, color: themeConfig.textTertiary, boxShadow: `0 6rpx 20rpx ${themeConfig.shadowColor || 'rgba(0,0,0,0.15)'}` }"
hover-class="cm-close--active"
:hover-stay-time="100"
@click="handleClose"
>
<FaIcon icon="xmark" :size="22" />
</view>
</view>
</view>
</template>
<script>
import FaIcon from './FaIcon.vue'
import themeMixin from '@/mixins/themeMixin.js'
export default {
name: 'CenterModal',
components: { FaIcon },
mixins: [themeMixin],
props: {
visible: { type: Boolean, default: false },
title: { type: String, default: '' },
showClose: { type: Boolean, default: true },
showCancel: { type: Boolean, default: true },
showConfirm: { type: Boolean, default: true },
maskClosable: { type: Boolean, default: true },
cancelText: { type: String, default: '取消' },
confirmText: { type: String, default: '确定' },
confirmColor: { type: String, default: '' },
confirmDisabled: { type: Boolean, default: false },
maxBodyHeight: { type: String, default: '70vh' }
},
computed: {
/**
* 确认按钮样式:直接应用主题渐变色(不依赖 CSS 变量),确保主题切换立即生效
*/
confirmBtnStyle() {
const bg = this.confirmColor || this.themeConfig.gradientSoft
return {
background: bg,
color: '#ffffff',
boxShadow: `0 6rpx 18rpx ${this.themeConfig.shadowColor || 'rgba(198, 142, 63, 0.35)'}`
}
},
cancelBg() {
// 跟随主题色:使用 inputBgColor 创造与主题协调的次按钮背景
return this.themeConfig.inputBgColor || 'rgba(0, 0, 0, 0.06)'
}
},
methods: {
handleMaskClick() {
if (!this.maskClosable) return
this.handleClose()
},
handleClose() {
if (!this.visible) return
this.$emit('update:visible', false)
this.$emit('close')
},
handleCancel() {
if (!this.visible) return
this.$emit('update:visible', false)
this.$emit('close')
this.$emit('cancel')
},
handleConfirm() {
if (!this.visible) return
if (this.confirmDisabled) return
this.$emit('confirm')
}
}
}
</script>
<style lang="scss" scoped>
.center-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 99999;
}
.cm-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
animation: cm-fadeIn 0.2s ease;
}
@keyframes cm-fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* 弹窗容器:用绝对定位 + transform 居中,简单可靠 */
.cm-dialog-wrap {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
align-items: center;
justify-content: center;
animation: cm-zoomIn 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes cm-zoomIn {
from { transform: translate(-50%, -50%) scale(0.92); opacity: 0; }
to { transform: translate(-50%, -50%) scale(1); opacity: 1; }
}
.cm-dialog {
position: relative;
width: 80vw;
max-width: 600rpx;
max-height: 85vh;
border-radius: 24rpx;
padding: 56rpx 40rpx 40rpx;
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.18);
display: flex;
flex-direction: column;
}
.cm-header {
text-align: center;
margin-bottom: 24rpx;
}
.cm-title {
font-size: 32rpx;
font-weight: 600;
line-height: 1.4;
}
.cm-body {
font-size: 28rpx;
line-height: 1.6;
min-height: 60rpx;
overflow-y: auto;
flex: 1;
}
.cm-footer {
display: flex;
justify-content: center;
gap: 24rpx;
margin-top: 40rpx;
}
/* 关闭按钮:紧贴弹窗右上角外侧 */
.cm-close {
position: absolute;
top: -28rpx;
right: -28rpx;
width: 64rpx;
height: 64rpx;
border-radius: 50%;
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
border: 2rpx solid rgba(0, 0, 0, 0.06);
transition: transform 0.2s ease, opacity 0.2s ease;
-webkit-tap-highlight-color: transparent;
}
.cm-close--active {
transform: scale(0.9);
opacity: 0.7;
}
/* 按钮:默认中等大小,不依赖 .btn 统一样式,避免被外部样式干扰 */
.cm-btn {
flex: 1;
min-height: 80rpx;
padding: 0 40rpx;
border-radius: 14rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 30rpx;
font-weight: 600;
border: none;
outline: none;
transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s ease;
-webkit-tap-highlight-color: transparent;
}
.cm-btn__text {
line-height: 1.2;
}
.cm-btn--cancel--active {
opacity: 0.75;
transform: scale(0.98);
}
.cm-btn--confirm--active {
opacity: 0.92;
transform: scale(0.98);
}
.cm-btn--disabled {
opacity: 0.5;
pointer-events: none;
}
.cm-btn:only-child {
flex: 1;
}
</style>