|
|
@@ -0,0 +1,257 @@
|
|
|
+<template>
|
|
|
+ <transition name="message-fade">
|
|
|
+ <div v-if="localVisible" class="message-overlay" @click.self="handleOverlayClick">
|
|
|
+ <div class="message-box" :style="zIndexStyle">
|
|
|
+
|
|
|
+ <div class="message-header">
|
|
|
+ <span class="message-title">{{ title }}</span>
|
|
|
+ <span v-if="showClose" class="message-close" @click.stop="close">✕</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="message-body">
|
|
|
+ <p class="message-text">{{ message }}</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="message-footer" v-if="showConfirm">
|
|
|
+ <div class="message-btn-group">
|
|
|
+ <button class="message-btn" @click="confirm">{{ confirmText }}</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </transition>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import Vue from 'vue';
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: "MessageDialog",
|
|
|
+ // 将原先在 data 中的可配置参数全部移到 props 中
|
|
|
+ props: {
|
|
|
+ visible: { type: Boolean, default: false },
|
|
|
+ title: { type: String, default: "温馨提示" },
|
|
|
+ message: { type: String, default: "" },
|
|
|
+ type: { type: String, default: "info" }, // 'info', 'success', 'warning', 'error'
|
|
|
+ duration: { type: Number, default: 3000 }, // 大于0则自动关闭
|
|
|
+ showClose: { type: Boolean, default: true },
|
|
|
+ showConfirm: { type: Boolean, default: true },
|
|
|
+ confirmText: { type: String, default: "确认" },
|
|
|
+ closeOnClickModal: { type: Boolean, default: false },
|
|
|
+
|
|
|
+ // 如果依然保留函数回调方式(主要用于命令式调用)
|
|
|
+ onClose: { type: Function, default: null },
|
|
|
+ onConfirm: { type: Function, default: null }
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ // 内部变量,避免直接修改 prop(visible) 报错
|
|
|
+ localVisible: this.visible,
|
|
|
+ zIndex: 2000,
|
|
|
+ timer: null
|
|
|
+ };
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ zIndexStyle() {
|
|
|
+ return {
|
|
|
+ zIndex: this.zIndex
|
|
|
+ };
|
|
|
+ }
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ // 监听外部传入的 visible
|
|
|
+ visible(newVal) {
|
|
|
+ this.localVisible = newVal;
|
|
|
+ if (newVal) {
|
|
|
+ this.startTimer();
|
|
|
+ } else {
|
|
|
+ this.clearTimer();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 监听内部 localVisible 的变化,并同步给外部组件
|
|
|
+ localVisible(newVal) {
|
|
|
+ this.$emit('update:visible', newVal);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ if (this.localVisible) {
|
|
|
+ this.startTimer();
|
|
|
+ }
|
|
|
+ // 实例化时增加全局 zIndex,解决堆叠问题
|
|
|
+ Vue.prototype.$msgZIndex = (Vue.prototype.$msgZIndex || 2000) + 1;
|
|
|
+ this.zIndex = Vue.prototype.$msgZIndex;
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ close() {
|
|
|
+ this.localVisible = false; // 触发隐藏动画
|
|
|
+
|
|
|
+ // 触发回调和事件
|
|
|
+ if (typeof this.onClose === "function") {
|
|
|
+ this.onClose();
|
|
|
+ }
|
|
|
+ this.$emit("close");
|
|
|
+ this.clearTimer();
|
|
|
+
|
|
|
+ // 等待 Vue 过渡动画(0.3s)结束后,如果是命令式调用则销毁 DOM
|
|
|
+ setTimeout(() => {
|
|
|
+ // 判断:如果该组件是作为独立实例挂载在全局的(没有父组件)
|
|
|
+ if (this.$parent === this.$root && this.$el && this.$el.parentNode) {
|
|
|
+ this.$el.parentNode.removeChild(this.$el);
|
|
|
+ this.$destroy();
|
|
|
+ }
|
|
|
+ }, 300);
|
|
|
+ },
|
|
|
+ confirm() {
|
|
|
+ if (typeof this.onConfirm === "function") {
|
|
|
+ this.onConfirm();
|
|
|
+ }
|
|
|
+ this.$emit("confirm");
|
|
|
+ this.close();
|
|
|
+ },
|
|
|
+ handleOverlayClick() {
|
|
|
+ if (this.closeOnClickModal) {
|
|
|
+ this.close();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ startTimer() {
|
|
|
+ this.clearTimer();
|
|
|
+ if (this.duration > 0) {
|
|
|
+ this.timer = setTimeout(() => {
|
|
|
+ this.close();
|
|
|
+ }, this.duration);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ clearTimer() {
|
|
|
+ if (this.timer) {
|
|
|
+ clearTimeout(this.timer);
|
|
|
+ this.timer = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ this.clearTimer();
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+/* CSS 保持不变 */
|
|
|
+.message-overlay {
|
|
|
+ position: fixed;
|
|
|
+ inset: 0;
|
|
|
+ background: rgba(0, 5, 20, 0.6);
|
|
|
+ backdrop-filter: blur(8px);
|
|
|
+ -webkit-backdrop-filter: blur(8px);
|
|
|
+ z-index: 2000;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ user-select: none;
|
|
|
+}
|
|
|
+
|
|
|
+.message-box {
|
|
|
+ position: relative;
|
|
|
+ min-width: 320px;
|
|
|
+ max-width: 480px;
|
|
|
+ background: radial-gradient(circle at 50% 120%, rgba(20, 45, 90, 0.98) 0%, rgba(10, 20, 40, 1) 100%);
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.05);
|
|
|
+ border-radius: 12px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+ box-shadow:
|
|
|
+ 0 10px 40px rgba(0, 0, 0, 0.4),
|
|
|
+ inset 0 0 15px rgba(80, 180, 255, 0.1),
|
|
|
+ inset 0 1px 1px rgba(255, 255, 255, 0.2);
|
|
|
+}
|
|
|
+
|
|
|
+.message-header {
|
|
|
+ height: 40px;
|
|
|
+ background: rgba(30, 60, 110, 0.9);
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 0 16px;
|
|
|
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.message-title {
|
|
|
+ color: #ffffff;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ letter-spacing: 1px;
|
|
|
+}
|
|
|
+
|
|
|
+.message-close {
|
|
|
+ cursor: pointer;
|
|
|
+ color: #ffffff;
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 1;
|
|
|
+ font-weight: 300;
|
|
|
+ opacity: 0.8;
|
|
|
+ transition: all 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.message-close:hover {
|
|
|
+ opacity: 1;
|
|
|
+ transform: scale(1.1);
|
|
|
+}
|
|
|
+
|
|
|
+.message-body {
|
|
|
+ padding: 24px 16px;
|
|
|
+ text-align: left;
|
|
|
+}
|
|
|
+
|
|
|
+.message-text {
|
|
|
+ color: #ffffff;
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 1.6;
|
|
|
+ margin: 0;
|
|
|
+ word-break: break-word;
|
|
|
+}
|
|
|
+
|
|
|
+.message-footer {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ background: transparent;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+.message-footer::after {
|
|
|
+ content: '';
|
|
|
+ width: 100%;
|
|
|
+ height: 1px;
|
|
|
+ background: #2A3B57;
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+}
|
|
|
+.message-btn-group {
|
|
|
+ padding: 16px 16px 16px 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.message-btn {
|
|
|
+ padding: 6px 20px;
|
|
|
+ background: #1a75ff;
|
|
|
+ border: none;
|
|
|
+ border-radius: 6px;
|
|
|
+ color: #ffffff;
|
|
|
+ font-size: 14px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s;
|
|
|
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
|
|
+}
|
|
|
+
|
|
|
+.message-btn:hover {
|
|
|
+ background: #409EFF;
|
|
|
+ box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
|
|
|
+}
|
|
|
+
|
|
|
+.message-fade-enter-active, .message-fade-leave-active {
|
|
|
+ transition: opacity 0.3s, transform 0.3s;
|
|
|
+}
|
|
|
+.message-fade-enter, .message-fade-leave-to {
|
|
|
+ opacity: 0;
|
|
|
+}
|
|
|
+.message-fade-enter .message-box, .message-fade-leave-to .message-box {
|
|
|
+ transform: scale(0.85) translateY(-20px);
|
|
|
+}
|
|
|
+</style>
|