|
|
@@ -0,0 +1,236 @@
|
|
|
+<template>
|
|
|
+ <div class="user-profile-container" ref="profileContainer">
|
|
|
+ <div class="user-info" @click="toggleDropdown">
|
|
|
+ <img :src="avatarUrl" alt="user-avatar" class="avatar" />
|
|
|
+ <span class="username">{{ username }}</span>
|
|
|
+ <span class="arrow-icon" :class="{ 'is-open': isOpen }">▼</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <transition name="dropdown-fade">
|
|
|
+ <div class="dropdown-menu" v-show="isOpen">
|
|
|
+ <div class="dropdown-item" @click="onChangePassword">
|
|
|
+ <i class="icon-key"></i> 修改密码
|
|
|
+ </div>
|
|
|
+ <div class="dropdown-divider"></div>
|
|
|
+ <div class="dropdown-item logout" @click="onLogout">
|
|
|
+ <i class="icon-logout"></i> 登出
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </transition>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+export default {
|
|
|
+ name: 'UserProfile',
|
|
|
+ // 注入 DashboardLayout 提供的全局弹窗管理器
|
|
|
+ inject: ['dialogManager'],
|
|
|
+ props: {
|
|
|
+ // 允许父组件传入用户名和头像,如果没有则使用默认值
|
|
|
+ username: {
|
|
|
+ type: String,
|
|
|
+ default: '今晚打老虎'
|
|
|
+ },
|
|
|
+ avatarUrl: {
|
|
|
+ type: String,
|
|
|
+ default: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png' // 默认占位头像
|
|
|
+ }
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ isOpen: false // 控制下拉菜单显隐
|
|
|
+ };
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ // 监听全局点击事件,用于实现“点击空白处关闭弹窗”
|
|
|
+ document.addEventListener('click', this.handleClickOutside);
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ // 组件销毁前移除监听,防止内存泄漏
|
|
|
+ document.removeEventListener('click', this.handleClickOutside);
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ toggleDropdown() {
|
|
|
+ this.isOpen = !this.isOpen;
|
|
|
+ },
|
|
|
+ // 点击外部关闭弹窗的逻辑
|
|
|
+ handleClickOutside(event) {
|
|
|
+ // 如果点击的区域不在当前组件的 DOM 范围内,且菜单是打开的,则关闭菜单
|
|
|
+ if (this.isOpen && this.$refs.profileContainer && !this.$refs.profileContainer.contains(event.target)) {
|
|
|
+ this.isOpen = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 2. 直接在这里调用弹窗方法
|
|
|
+ onChangePassword() {
|
|
|
+ this.isOpen = false; // 先收起下拉菜单
|
|
|
+
|
|
|
+ // 直接打开修改密码弹窗
|
|
|
+ this.dialogManager.openDialog({
|
|
|
+ id: 'dialog-change-password',
|
|
|
+ title: '修改密码',
|
|
|
+ component: 'ChangePassword',
|
|
|
+ width: 400,
|
|
|
+ height: 380,
|
|
|
+ center: true,
|
|
|
+ data: {
|
|
|
+ // 成功回调,自动关闭弹窗
|
|
|
+ onSuccess: (formData) => {
|
|
|
+ console.log('密码已修改', formData);
|
|
|
+ // 这里可以写调接口的逻辑
|
|
|
+ this.dialogManager.closeDialog('dialog-change-password');
|
|
|
+ },
|
|
|
+ // 取消回调,自动关闭弹窗
|
|
|
+ onCancel: () => {
|
|
|
+ this.dialogManager.closeDialog('dialog-change-password');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ onLogout() {
|
|
|
+ this.isOpen = false;
|
|
|
+ // 向父组件派发事件,由父组件执行登出接口和路由跳转
|
|
|
+ this.$emit('logout');
|
|
|
+ this.$router.push("/login");
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+/* 容器相对定位,方便下拉菜单绝对定位 */
|
|
|
+.user-profile-container {
|
|
|
+ position: relative;
|
|
|
+ display: inline-block;
|
|
|
+}
|
|
|
+
|
|
|
+/* 顶部触发区样式 */
|
|
|
+.user-info {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ cursor: pointer;
|
|
|
+ padding: 5px 10px;
|
|
|
+ border-radius: 4px;
|
|
|
+ color: #ffffff;
|
|
|
+ user-select: none;
|
|
|
+ transition: background-color 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.user-info:hover {
|
|
|
+ background-color: rgba(255, 255, 255, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.avatar {
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ border-radius: 50%;
|
|
|
+ object-fit: cover;
|
|
|
+ margin-right: 8px;
|
|
|
+ /* 增加微弱的边框,让图片在深色背景下更有层次 */
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.2);
|
|
|
+}
|
|
|
+
|
|
|
+.username {
|
|
|
+ font-size: 14px;
|
|
|
+ margin-right: 6px;
|
|
|
+ letter-spacing: 0.5px;
|
|
|
+}
|
|
|
+
|
|
|
+.arrow-icon {
|
|
|
+ font-size: 10px;
|
|
|
+ transform: scale(0.8); /* 让自带的 ▼ 符号稍微小一点 */
|
|
|
+ transition: transform 0.3s ease;
|
|
|
+ display: inline-block;
|
|
|
+}
|
|
|
+
|
|
|
+/* 箭头旋转动画 */
|
|
|
+.arrow-icon.is-open {
|
|
|
+ transform: scale(0.8) rotate(180deg);
|
|
|
+}
|
|
|
+
|
|
|
+/* ================= 下拉菜单样式 ================= */
|
|
|
+.dropdown-menu {
|
|
|
+ position: absolute;
|
|
|
+ top: 100%;
|
|
|
+ right: 0;
|
|
|
+ margin-top: 10px;
|
|
|
+ min-width: 120px;
|
|
|
+ z-index: 1000;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ /* 完全对齐 SmartDialog 的背景与边框风格 */
|
|
|
+ background: radial-gradient(circle at 20% 0%, rgba(40,120,200,0.2) 0%, rgba(20,60,130,0.4) 70%);
|
|
|
+ box-shadow:
|
|
|
+ inset 0px 0px 10px 0px rgba(88, 146, 255, 0.4),
|
|
|
+ inset 20px 0px 30px -10px rgba(88, 146, 255, 0.15),
|
|
|
+ 0 4px 16px rgba(0, 0, 0, 0.5); /* 增加外阴影让其悬浮感更强 */
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.15);
|
|
|
+ border-radius: 8px; /* 下拉框稍微小一点的圆角 */
|
|
|
+ backdrop-filter: blur(8px);
|
|
|
+ -webkit-backdrop-filter: blur(8px);
|
|
|
+
|
|
|
+ transform-origin: top right;
|
|
|
+}
|
|
|
+
|
|
|
+/* 向上指的小三角(配合科技风调整颜色) */
|
|
|
+.dropdown-menu::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: -6px;
|
|
|
+ right: 25px;
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ border-left: 6px solid transparent;
|
|
|
+ border-right: 6px solid transparent;
|
|
|
+ /* 颜色贴近顶部边框的浅蓝色 */
|
|
|
+ border-bottom: 6px solid rgba(88, 146, 255, 0.4);
|
|
|
+}
|
|
|
+
|
|
|
+.dropdown-item {
|
|
|
+ padding: 12px 16px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #e2e8f0; /* 改为浅色文字 */
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.dropdown-item:hover {
|
|
|
+ /* 悬浮时使用科技蓝半透明高亮 */
|
|
|
+ background-color: rgba(59, 116, 255, 0.3);
|
|
|
+ color: #ffffff;
|
|
|
+}
|
|
|
+
|
|
|
+/* 登出按钮特殊处理:悬浮变红 */
|
|
|
+.dropdown-item.logout {
|
|
|
+ color: #e2e8f0;
|
|
|
+}
|
|
|
+.dropdown-item.logout:hover {
|
|
|
+ background-color: rgba(255, 77, 79, 0.2); /* 半透明红色警示 */
|
|
|
+ color: #ff4d4f;
|
|
|
+}
|
|
|
+
|
|
|
+.dropdown-divider {
|
|
|
+ height: 1px;
|
|
|
+ /* 分割线颜色调整为半透明白 */
|
|
|
+ background-color: rgba(255, 255, 255, 0.15);
|
|
|
+ margin: 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* 展开时的动画:使用 cubic-bezier 制造轻微的 Q 弹物理效果 */
|
|
|
+.dropdown-fade-enter-active {
|
|
|
+ transition: opacity 0.3s ease, transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
|
+}
|
|
|
+
|
|
|
+/* 收起时的动画:干脆利落,不拖泥带水 */
|
|
|
+.dropdown-fade-leave-active {
|
|
|
+ transition: opacity 0.2s ease, transform 0.2s cubic-bezier(0.4, 0, 1, 1);
|
|
|
+}
|
|
|
+
|
|
|
+/* 初始状态和结束状态:透明度为0,缩小至 80%,并稍微向上偏移 */
|
|
|
+.dropdown-fade-enter, .dropdown-fade-leave-to {
|
|
|
+ opacity: 0;
|
|
|
+ transform: scale(0.8) translateY(-10px);
|
|
|
+}
|
|
|
+</style>
|