396 lines
7.0 KiB
Vue
396 lines
7.0 KiB
Vue
<template>
|
|
<div v-if="visible" class="modal-overlay" @click="handleOverlayClick">
|
|
<div class="modal" :class="[animationClass, modalTypeClass]">
|
|
<!-- 图标 - simple 类型不显示 -->
|
|
<div class="modal-icon" v-if="!isSimple">
|
|
<div class="icon-circle" :class="iconCircleClass">
|
|
<span class="icon-symbol" :class="iconSymbolClass">{{ iconSymbol }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 标题 - simple 类型不显示 -->
|
|
<div class="modal-title" v-if="title && !isSimple">{{ title }}</div>
|
|
|
|
<!-- 消息内容 -->
|
|
<div class="modal-message">{{ message }}</div>
|
|
|
|
<!-- 按钮 -->
|
|
<div class="modal-actions">
|
|
<button class="modal-btn" :class="buttonClass" @click="confirm">{{ confirmText }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted, computed } from 'vue'
|
|
|
|
const props = defineProps({
|
|
title: {
|
|
type: String,
|
|
default: 'Notice',
|
|
},
|
|
message: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
type: {
|
|
type: String,
|
|
default: 'error', // 'error' | 'warning' | 'success' | 'info'
|
|
validator: (value) => ['error', 'warning', 'success', 'info', 'simple'].includes(value), // 新增 simple
|
|
},
|
|
confirmText: {
|
|
type: String,
|
|
default: 'OK',
|
|
},
|
|
clickToClose: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
duration: {
|
|
type: Number,
|
|
default: null, // 自动关闭时间(毫秒)
|
|
},
|
|
})
|
|
|
|
const emit = defineEmits(['confirm', 'close'])
|
|
|
|
const visible = ref(false)
|
|
const animationClass = ref('')
|
|
|
|
// 新增计算属性,判断是否显示图标和标题
|
|
const isSimple = computed(() => props.type === 'simple')
|
|
|
|
// 根据类型计算图标和样式
|
|
const iconSymbol = computed(() => {
|
|
const symbols = {
|
|
error: '✕',
|
|
warning: '⚠',
|
|
success: '✓',
|
|
info: 'i',
|
|
}
|
|
return symbols[props.type] || symbols.error
|
|
})
|
|
|
|
const modalTypeClass = computed(() => `modal-${props.type}`)
|
|
|
|
const iconCircleClass = computed(() => `icon-circle-${props.type}`)
|
|
|
|
const iconSymbolClass = computed(() => `icon-symbol-${props.type}`)
|
|
|
|
const buttonClass = computed(() => `modal-btn-${props.type}`)
|
|
|
|
const show = () => {
|
|
visible.value = true
|
|
animationClass.value = 'modal-enter'
|
|
|
|
// 如果设置了自动关闭时间
|
|
if (props.duration && props.duration > 0) {
|
|
setTimeout(() => {
|
|
close()
|
|
}, props.duration)
|
|
}
|
|
}
|
|
|
|
const close = () => {
|
|
animationClass.value = 'modal-leave'
|
|
setTimeout(() => {
|
|
visible.value = false
|
|
emit('close')
|
|
}, 200)
|
|
}
|
|
|
|
const confirm = () => {
|
|
emit('confirm')
|
|
close()
|
|
}
|
|
|
|
const handleOverlayClick = () => {
|
|
if (props.clickToClose) {
|
|
close()
|
|
}
|
|
}
|
|
|
|
// 组件挂载时显示
|
|
onMounted(() => {
|
|
show()
|
|
})
|
|
|
|
// 暴露方法给父组件
|
|
defineExpose({
|
|
show,
|
|
close,
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* 基础模态框样式 */
|
|
.modal-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background-color: rgba(0, 0, 0, 0.6);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 9999;
|
|
padding: 20px;
|
|
}
|
|
|
|
.modal {
|
|
background: white;
|
|
border-radius: 16px;
|
|
box-shadow:
|
|
0 20px 25px rgba(0, 0, 0, 0.1),
|
|
0 10px 10px rgba(0, 0, 0, 0.04);
|
|
width: 100%;
|
|
max-width: 320px;
|
|
padding: 24px 20px 20px;
|
|
text-align: center;
|
|
position: relative;
|
|
}
|
|
|
|
/* 图标区域 */
|
|
.modal-icon {
|
|
margin-bottom: 16px;
|
|
display: flex;
|
|
justify-content: center;
|
|
}
|
|
|
|
.icon-circle {
|
|
width: 56px;
|
|
height: 56px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
animation: iconPulse 0.6s ease-out;
|
|
border: 3px solid;
|
|
}
|
|
|
|
.icon-symbol {
|
|
font-size: 24px;
|
|
font-weight: bold;
|
|
line-height: 1;
|
|
}
|
|
|
|
/* 标题 */
|
|
.modal-title {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: #1f2937;
|
|
margin-bottom: 12px;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
/* 消息内容 */
|
|
.modal-message {
|
|
font-size: 14px;
|
|
color: #6b7280;
|
|
line-height: 1.5;
|
|
margin-bottom: 20px;
|
|
word-wrap: break-word;
|
|
}
|
|
|
|
/* 按钮区域 */
|
|
.modal-actions {
|
|
display: flex;
|
|
justify-content: center;
|
|
}
|
|
|
|
.modal-btn {
|
|
border: none;
|
|
border-radius: 8px;
|
|
padding: 10px 32px;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
min-width: 80px;
|
|
color: white;
|
|
}
|
|
|
|
.modal-btn:active {
|
|
transform: scale(0.98);
|
|
}
|
|
|
|
/* Error 类型样式 */
|
|
.modal-error .icon-circle-error {
|
|
background-color: #fee2e2;
|
|
border-color: #fecaca;
|
|
}
|
|
|
|
.modal-error .icon-symbol-error {
|
|
color: #dc2626;
|
|
}
|
|
|
|
.modal-error .modal-btn-error {
|
|
background-color: #dc2626;
|
|
}
|
|
|
|
.modal-error .modal-btn-error:hover {
|
|
background-color: #b91c1c;
|
|
}
|
|
|
|
.modal-error .modal-btn-error:active {
|
|
background-color: #991b1b;
|
|
}
|
|
|
|
/* Warning 类型样式 */
|
|
.modal-warning .icon-circle-warning {
|
|
background-color: #fef3c7;
|
|
border-color: #fde68a;
|
|
}
|
|
|
|
.modal-warning .icon-symbol-warning {
|
|
color: #d97706;
|
|
}
|
|
|
|
.modal-warning .modal-btn-warning {
|
|
background-color: #d97706;
|
|
}
|
|
|
|
.modal-warning .modal-btn-warning:hover {
|
|
background-color: #b45309;
|
|
}
|
|
|
|
.modal-warning .modal-btn-warning:active {
|
|
background-color: #92400e;
|
|
}
|
|
|
|
/* Success 类型样式 */
|
|
.modal-success .icon-circle-success {
|
|
background-color: #d1fae5;
|
|
border-color: #a7f3d0;
|
|
}
|
|
|
|
.modal-success .icon-symbol-success {
|
|
color: #059669;
|
|
}
|
|
|
|
.modal-success .modal-btn-success {
|
|
background-color: #059669;
|
|
}
|
|
|
|
.modal-success .modal-btn-success:hover {
|
|
background-color: #047857;
|
|
}
|
|
|
|
.modal-success .modal-btn-success:active {
|
|
background-color: #065f46;
|
|
}
|
|
|
|
/* Info 类型样式 */
|
|
.modal-info .icon-circle-info {
|
|
background-color: #dbeafe;
|
|
border-color: #bfdbfe;
|
|
}
|
|
|
|
.modal-info .icon-symbol-info {
|
|
color: #2563eb;
|
|
}
|
|
|
|
.modal-info .modal-btn-info {
|
|
background-color: #2563eb;
|
|
}
|
|
|
|
.modal-info .modal-btn-info:hover {
|
|
background-color: #1d4ed8;
|
|
}
|
|
|
|
.modal-info .modal-btn-info:active {
|
|
background-color: #1e40af;
|
|
}
|
|
|
|
/* 新增 simple 类型样式 */
|
|
.modal-simple .modal-message {
|
|
font-size: 15px;
|
|
color: #374151;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.modal-simple .modal-btn {
|
|
background-color: #2563eb;
|
|
}
|
|
|
|
.modal-simple .modal-btn:hover {
|
|
background-color: #1d4ed8;
|
|
}
|
|
|
|
.modal-simple .modal-btn:active {
|
|
background-color: #1e40af;
|
|
}
|
|
|
|
/* 动画效果 */
|
|
.modal-enter {
|
|
animation: modalEnter 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
}
|
|
|
|
.modal-leave {
|
|
animation: modalLeave 0.2s ease-in;
|
|
}
|
|
|
|
@keyframes modalEnter {
|
|
0% {
|
|
opacity: 0;
|
|
transform: scale(0.7) translateY(-10px);
|
|
}
|
|
100% {
|
|
opacity: 1;
|
|
transform: scale(1) translateY(0);
|
|
}
|
|
}
|
|
|
|
@keyframes modalLeave {
|
|
0% {
|
|
opacity: 1;
|
|
transform: scale(1) translateY(0);
|
|
}
|
|
100% {
|
|
opacity: 0;
|
|
transform: scale(0.9) translateY(-5px);
|
|
}
|
|
}
|
|
|
|
@keyframes iconPulse {
|
|
0% {
|
|
transform: scale(0.8);
|
|
opacity: 0.5;
|
|
}
|
|
50% {
|
|
transform: scale(1.1);
|
|
}
|
|
100% {
|
|
transform: scale(1);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
/* 响应式设计 */
|
|
@media (max-width: 480px) {
|
|
.modal {
|
|
max-width: 280px;
|
|
padding: 20px 16px 16px;
|
|
}
|
|
|
|
.icon-circle {
|
|
width: 48px;
|
|
height: 48px;
|
|
}
|
|
|
|
.icon-symbol {
|
|
font-size: 20px;
|
|
}
|
|
|
|
.modal-title {
|
|
font-size: 16px;
|
|
}
|
|
|
|
.modal-message {
|
|
font-size: 13px;
|
|
}
|
|
}
|
|
</style>
|