aslan-h5/src/components/Toast.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>