feat: user-bar 修改

This commit is contained in:
2026-06-22 10:32:53 +08:00
parent 0b7a89c06b
commit 7d2d6707b7
10 changed files with 400 additions and 586 deletions

View File

@@ -1,23 +1,117 @@
<template>
<div class="user-bar">
<div class="container">
<el-popover trigger="click" :width="260">
<el-popover
trigger="click"
:width="280"
:show-arrow="false"
:offset="14"
placement="top"
popper-class="user-popover"
>
<template #reference>
<div class="user-info">
<el-badge is-dot :offset="[-4, 20]" class="user-badge">
<el-avatar
:size="28"
shape="circle"
src="https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg"
/>
<el-avatar :size="28" shape="circle" :src="userAvatar" />
</el-badge>
<div class="user-name">
<div class="user-title">MarchGlow</div>
<div class="user-state">在线</div>
<div class="user-title">{{ displayName }}</div>
<div class="user-state">{{ currentStatus.label }}</div>
</div>
</div>
</template>
<span>I wish they all could be California girls</span>
<!-- 弹出面板内容 -->
<div class="user-panel">
<!-- 顶部大头像 + 昵称 + 在线状态 -->
<div class="user-panel__header">
<el-avatar :size="56" shape="circle" :src="userAvatar" class="user-panel__avatar" />
<div class="user-panel__identity">
<div class="user-panel__nickname">{{ displayName }}</div>
<div class="user-panel__status">
<span
class="user-panel__status-dot"
:style="{ backgroundColor: currentStatus.color }"
></span>
<span>{{ currentStatus.label }}</span>
</div>
</div>
</div>
<!-- 个性签名 -->
<div class="user-panel__signature">
{{ signatureText }}
</div>
<div class="user-panel__divider"></div>
<!-- 详细设置区编辑个人资料 + 在线状态 -->
<div class="user-panel__section">
<div class="user-panel__item" @click="handleEditProfile">
<el-icon :size="18"><EditPen /></el-icon>
<span>编辑个人资料</span>
</div>
<!-- 在线状态父项 + 二级菜单 -->
<div class="user-panel__status-group">
<div class="user-panel__item" @click="toggleStatusMenu">
<span
class="user-panel__status-dot"
:style="{ backgroundColor: currentStatus.color }"
></span>
<span>在线状态</span>
<span class="user-panel__status-current">{{ currentStatus.label }}</span>
<el-icon
:size="14"
class="user-panel__chevron"
:class="{ 'is-open': showStatusMenu }"
>
<ArrowRight />
</el-icon>
</div>
<!-- 二级菜单状态选项列表 -->
<Transition name="status-menu">
<div v-show="showStatusMenu" class="user-panel__status-submenu">
<div
v-for="status in statusOptions"
:key="status.key"
class="user-panel__status-option"
:class="{ 'is-active': currentStatus.key === status.key }"
@click="selectStatus(status)"
>
<span
class="user-panel__status-dot"
:style="{ backgroundColor: status.color }"
></span>
<span>{{ status.label }}</span>
<el-icon
v-if="currentStatus.key === status.key"
:size="14"
class="user-panel__check"
>
<Check />
</el-icon>
</div>
</div>
</Transition>
</div>
</div>
<div class="user-panel__divider"></div>
<!-- 操作区复制账号ID + 退出登录 -->
<div class="user-panel__section">
<div class="user-panel__item" @click="handleCopyUserId">
<el-icon :size="18"><DocumentCopy /></el-icon>
<span>复制账号ID</span>
</div>
<div class="user-panel__item user-panel__item--danger" @click="handleLogout">
<el-icon :size="18"><SwitchButton /></el-icon>
<span>退出登录</span>
</div>
</div>
</div>
</el-popover>
<div class="feature">
@@ -28,10 +122,111 @@
</template>
<script setup lang="ts">
import { Setting } from '@element-plus/icons-vue'
import { computed, ref } from 'vue'
import { storeToRefs } from 'pinia'
import { ElMessage } from 'element-plus'
import {
Setting,
EditPen,
ArrowRight,
Check,
DocumentCopy,
SwitchButton
} from '@element-plus/icons-vue'
import { useAccountStore } from '@/stores'
/** 在线状态选项 */
interface StatusOption {
key: string
label: string
color: string
}
const STATUS_OPTIONS: StatusOption[] = [
{ key: 'online', label: '在线', color: 'var(--color-success)' },
{ key: 'away', label: '离开', color: 'var(--color-warning)' },
{ key: 'dnd', label: '请勿打扰', color: 'var(--color-danger)' },
{ key: 'invisible', label: '隐身', color: 'var(--text-tertiary)' }
]
const accountStore = useAccountStore()
const { accountInfo } = storeToRefs(accountStore)
/** 当前在线状态(默认:在线) */
const currentStatus = ref<StatusOption>(STATUS_OPTIONS[0]!)
/** 状态二级菜单是否展开 */
const showStatusMenu = ref(false)
/** 在线状态选项列表 */
const statusOptions = STATUS_OPTIONS
/** 用户头像(优先使用 store 中的头像,否则使用默认占位) */
const userAvatar = computed(() => {
return accountInfo.value?.avatar || ''
})
/** 显示昵称(未登录或未加载时显示默认文案) */
const displayName = computed(() => {
return accountInfo.value?.nickName || '未登录'
})
/** 个性签名(无签名时显示默认文案) */
const signatureText = computed(() => {
return accountInfo.value?.signature || '这个人很懒,什么都没写...'
})
/**
* 编辑个人资料
* 预留功能入口,后续可弹出编辑弹窗或跳转到设置页
*/
const handleEditProfile = () => {
ElMessage.info('编辑个人资料功能开发中')
}
/** 展开/收起在线状态二级菜单 */
const toggleStatusMenu = () => {
showStatusMenu.value = !showStatusMenu.value
}
/**
* 选择一个在线状态
* @param status - 选中的状态选项
*/
const selectStatus = (status: StatusOption) => {
currentStatus.value = status
showStatusMenu.value = false
}
/**
* 复制账号ID到剪贴板
* 复制用户的 UUID 标识符
*/
const handleCopyUserId = async () => {
const userId = accountInfo.value?.userId
if (!userId) {
ElMessage.warning('账号ID获取失败请刷新后重试')
return
}
try {
await navigator.clipboard.writeText(userId)
ElMessage.success('账号ID已复制到剪贴板')
} catch {
ElMessage.error('复制失败,请手动复制')
}
}
/**
* 退出登录
* 调用 store 清除认证状态,后续可跳转至登录页
*/
const handleLogout = () => {
accountStore.logout()
ElMessage.success('已退出登录')
}
</script>
<style scoped lang="scss">
/* ========== user-bar 容器 ========== */
.user-bar {
display: flex;
width: 100%;
@@ -47,9 +242,8 @@ import { Setting } from '@element-plus/icons-vue'
width: 12px;
height: 12px;
}
.user-badge :deep(.el-badge__content.is-dot) {
background-color: var(--color-success);
background-color: v-bind('currentStatus.color');
}
.container {
@@ -80,7 +274,6 @@ import { Setting } from '@element-plus/icons-vue'
display: flex;
flex-direction: column;
gap: 2px;
/* 默认内容宽度,超长时上限 160px 触发省略 */
max-width: 160px;
min-width: 0;
}
@@ -122,16 +315,199 @@ import { Setting } from '@element-plus/icons-vue'
background-color: var(--bg-surface-hover);
color: var(--text-primary);
/* 移入时缓动旋转 180°完成后保持 */
:deep(.el-icon) {
transition: transform 0.6s cubic-bezier(0.6, 0, 1, 1);
transform: rotate(180deg);
}
}
/* 移出时无过渡,瞬间复位 */
:deep(.el-icon) {
transition: none;
}
}
/* ========== 弹出面板 ========== */
.user-panel {
display: flex;
flex-direction: column;
}
/* ---- 顶部头像区 ---- */
.user-panel__header {
display: flex;
align-items: center;
gap: 12px;
padding: 4px 0 12px;
}
.user-panel__avatar {
flex-shrink: 0;
}
.user-panel__identity {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
}
.user-panel__nickname {
font-size: 16px;
font-weight: 700;
color: var(--text-primary);
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.user-panel__status {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--text-secondary);
}
.user-panel__status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
/* ---- 签名区 ---- */
.user-panel__signature {
padding: 8px 0;
font-size: 13px;
line-height: 1.5;
color: var(--text-secondary);
word-break: break-word;
}
/* ---- 分隔线 ---- */
.user-panel__divider {
height: 1px;
background-color: var(--border-light);
margin: 0;
}
/* ---- 操作区 ---- */
.user-panel__section {
display: flex;
flex-direction: column;
padding: 4px 0;
}
.user-panel__item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 8px;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 14px;
color: var(--text-primary);
transition: background-color 0.12s ease;
&:hover {
background-color: var(--bg-surface-hover);
}
}
/* 危险操作(退出登录) */
.user-panel__item--danger {
color: var(--color-danger);
&:hover {
background-color: var(--color-danger-light);
}
}
/* ---- 在线状态组(父项 + 二级菜单) ---- */
.user-panel__status-group {
display: flex;
flex-direction: column;
}
/* 当前选中状态标签(显示在父项右侧) */
.user-panel__status-current {
margin-left: auto;
font-size: 12px;
color: var(--text-tertiary);
}
/* 父项中的箭头图标 */
.user-panel__chevron {
color: var(--text-tertiary);
transition: transform 0.2s ease;
&.is-open {
transform: rotate(90deg);
}
}
/* ---- 二级菜单容器 ---- */
.user-panel__status-submenu {
display: flex;
flex-direction: column;
padding: 2px 0 4px 28px; /* 左侧缩进,与父项文字对齐 */
}
/* 二级菜单单项 */
.user-panel__status-option {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 8px;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 13px;
color: var(--text-primary);
transition: background-color 0.12s ease;
&:hover {
background-color: var(--bg-surface-hover);
}
}
/* 当前选中项的对勾图标 */
.user-panel__check {
margin-left: auto;
color: var(--color-primary);
}
/* ---- 二级菜单展开/收起动画 ---- */
.status-menu-enter-active {
transition:
max-height 0.25s ease,
opacity 0.2s ease;
overflow: hidden;
}
.status-menu-leave-active {
transition:
max-height 0.2s ease,
opacity 0.15s ease;
overflow: hidden;
}
.status-menu-enter-from,
.status-menu-leave-to {
max-height: 0;
opacity: 0;
}
.status-menu-enter-to,
.status-menu-leave-from {
max-height: 200px;
opacity: 1;
}
</style>
<!-- 覆盖 el-popover 默认样式 scoped因为 popover 渲染在 body -->
<style lang="scss">
.user-popover {
padding: 8px 12px !important;
border-radius: var(--radius-md) !important;
box-shadow: var(--shadow-lg) !important;
border: 1px solid var(--border-default) !important;
}
</style>