Bläddra i källkod

新增MessageDialog消息弹窗;修改首页登录的错误消息提醒;修改main.js挂载全局MessageDialog方法;

画安 1 månad sedan
förälder
incheckning
7f774d9ba5

+ 257 - 0
src/components/ui/MessageDialog/MessageDialog.vue

@@ -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>

+ 42 - 0
src/components/ui/MessageDialog/index.js

@@ -0,0 +1,42 @@
+import Vue from 'vue';
+import MessageDialogComponent from './MessageDialog.vue';
+
+// 创建组件构造器
+const MessageConstructor = Vue.extend(MessageDialogComponent);
+
+const MessageDialog = function(options = {}) {
+  // 允许直接传入字符串作为 message
+  if (typeof options === 'string') {
+    options = { message: options };
+  }
+
+  // 实例化组件
+  const instance = new MessageConstructor({
+    propsData: options
+  });
+
+  // 挂载并插入 DOM
+  instance.$mount();
+  document.body.appendChild(instance.$el);
+  
+  // 触发显示动画
+  Vue.nextTick(() => {
+    instance.visible = true;
+  });
+
+  return instance;
+};
+
+// 封装快捷调用方法:$coolMsg.error('...')
+['success', 'warning', 'info', 'error'].forEach(type => {
+  MessageDialog[type] = options => {
+    if (typeof options === 'string') {
+      options = { message: options, type };
+    } else {
+      options.type = type;
+    }
+    return MessageDialog(options);
+  };
+});
+
+export default MessageDialog;

+ 4 - 0
src/main.js

@@ -3,6 +3,10 @@ import App from "./App.vue";
 import router from "./router";
 import "./styles/base.css";
 import '@/utils/rem.js';
+import MessageDialog from "./components/ui/MessageDialog/index.js";
+
+// 挂载为全局方法
+Vue.prototype.$msg = MessageDialog;
 
 Vue.config.productionTip = false;
 

+ 7 - 1
src/views/Login.vue

@@ -117,7 +117,13 @@ export default {
         captcha: this.captchaInput
       });
 
-      if (!res.ok) return window.$toast.error(res.message);
+      if (!res.ok) {
+        this.$msg.error({
+          message: res.message,
+          duration: 0
+        });
+        return;
+      }
 
       console.log('123131');
       this.doorNavigated = false;