456 lines
16 KiB
JavaScript
Raw Normal View History

2025-04-28 12:25:20 +08:00
class UserInfo extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
this.addEventListeners();
this.checkLoginStatus();
}
render() {
this.shadowRoot.innerHTML = `
<style>
.user-dropdown {
height: 100%;
position: relative;
border-left: 1px solid #e0e0e0;
}
.user-dropdown-toggle {
height: 100%;
display: flex;
align-items: center;
padding: 0 20px;
cursor: pointer;
transition: background-color 0.3s;
gap: 10px;
position: relative;
}
.user-dropdown-toggle:hover {
background-color: #f5f5f5;
}
.user-dropdown-toggle img.icon-small {
width: 12px;
height: 12px;
opacity: 0.5;
}
.user-dropdown-toggle img:not(.icon-small) {
width: 32px;
height: 32px;
border-radius: 50%;
}
.user-dropdown-toggle span {
font-size: 17px;
color: #333;
font-weight: 500;
}
.user-icon {
2025-04-28 12:25:20 +08:00
width: 24px;
height: 24px;
margin-right: 0;
vertical-align: middle;
cursor: help;
}
.user-icon-wrapper {
2025-04-28 12:25:20 +08:00
position: relative;
display: inline-block;
z-index: 1002;
padding: 0;
margin: 0;
2025-04-28 12:25:20 +08:00
}
.icon-small {
width: 12px;
height: 12px;
opacity: 0.7;
}
.user-dropdown-menu {
position: absolute;
top: 100%;
right: 0;
background: white;
border: 1px solid #e0e0e0;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
display: none;
z-index: 1000;
min-width: 150px;
max-width: 250px;
white-space: nowrap;
overflow: hidden;
2025-04-28 12:25:20 +08:00
}
.user-dropdown.active .user-dropdown-menu {
display: block;
}
.dropdown-item {
display: flex;
align-items: center;
padding: 8px 16px;
cursor: pointer;
transition: background-color 0.3s;
gap: 8px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
2025-04-28 12:25:20 +08:00
}
.dropdown-item:hover {
background-color: #f5f5f5;
}
.dropdown-item .icon-small {
width: 16px;
height: 16px;
opacity: 0.7;
}
.dropdown-divider {
height: 1px;
background-color: #e0e0e0;
margin: 4px 0;
}
.tooltip {
position: absolute;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 8px 12px;
2025-04-28 12:25:20 +08:00
border-radius: 4px;
font-size: 13px;
2025-04-28 12:25:20 +08:00
visibility: hidden;
opacity: 0;
transition: opacity 0.3s;
top: 100%;
left: 50%;
transform: translateX(-50%);
margin-top: 8px;
2025-04-28 12:25:20 +08:00
z-index: 1001;
min-width: 200px;
text-align: left;
line-height: 1.4;
2025-04-28 12:25:20 +08:00
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
.tooltip.right-aligned {
left: auto;
right: 0;
transform: none;
margin: 8px 0 0 0;
padding: 8px 12px 8px 12px;
}
2025-04-28 12:25:20 +08:00
.tooltip .info-row {
margin: 2px 0;
display: flex;
align-items: center;
gap: 8px;
white-space: nowrap;
2025-04-28 12:25:20 +08:00
}
.tooltip .label {
color: #a8c8ff;
flex-shrink: 0;
min-width: 70px;
2025-04-28 12:25:20 +08:00
}
.tooltip .value {
color: #ffffff;
flex: 1;
white-space: nowrap;
}
.user-icon-wrapper:hover .tooltip {
2025-04-28 12:25:20 +08:00
visibility: visible;
opacity: 1;
}
.tooltip::before {
content: '';
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
border-width: 0 5px 5px 5px;
border-style: solid;
border-color: transparent transparent rgba(0, 0, 0, 0.8) transparent;
}
.dropdown-item span {
overflow: hidden;
text-overflow: ellipsis;
}
2025-04-28 12:25:20 +08:00
</style>
<div class="user-dropdown">
<div class="user-dropdown-toggle">
<div class="user-icon-wrapper">
<img id="userIcon" src="assets/icons/png/user.png" alt="用户" class="user-icon">
2025-04-28 12:25:20 +08:00
<div class="tooltip" id="userTooltip">用户信息加载中...</div>
</div>
<span id="userName">用户名</span>
<img src="assets/icons/png/chevron-down_b.png" alt="展开" class="icon-small">
</div>
<div class="user-dropdown-menu">
<div class="dropdown-item" data-action="profile">
<img src="assets/icons/png/user_b.png" alt="个人中心" class="icon-small">
个人中心
</div>
<div class="dropdown-item admin-only" data-action="users">
<img src="assets/icons/png/users_b.png" alt="用户管理" class="icon-small">
用户管理
</div>
<div class="dropdown-divider"></div>
<div class="dropdown-item" data-action="logout">
<img src="assets/icons/png/logout_b.png" alt="退出登录" class="icon-small">
退出登录
</div>
</div>
</div>
`;
}
addEventListeners() {
const userDropdown = this.shadowRoot.querySelector('.user-dropdown');
const userDropdownToggle = this.shadowRoot.querySelector('.user-dropdown-toggle');
const userIconWrapper = this.shadowRoot.querySelector('.user-icon-wrapper');
2025-04-28 12:25:20 +08:00
// 点击切换下拉菜单
userDropdownToggle.addEventListener('click', (e) => {
e.stopPropagation();
userDropdown.classList.toggle('active');
});
// 点击其他地方关闭下拉菜单
document.addEventListener('click', () => {
userDropdown.classList.remove('active');
});
// 处理下拉菜单项点击
const dropdownItems = this.shadowRoot.querySelectorAll('.dropdown-item');
dropdownItems.forEach(item => {
item.addEventListener('click', (e) => {
e.stopPropagation();
const action = item.getAttribute('data-action');
this.handleDropdownAction(action);
userDropdown.classList.remove('active');
});
});
// 处理tooltip的定位
userIconWrapper.addEventListener('mouseenter', () => {
const tooltip = this.shadowRoot.querySelector('.tooltip');
const rect = userIconWrapper.getBoundingClientRect();
const tooltipRect = tooltip.getBoundingClientRect();
// 检查右边界
if (rect.left + tooltipRect.width > window.innerWidth) {
// 计算tooltip应该距离右边界的位置
const rightEdge = window.innerWidth;
const tooltipWidth = tooltipRect.width;
tooltip.style.position = 'fixed';
tooltip.style.left = 'auto';
tooltip.style.right = '0';
tooltip.style.transform = 'none';
tooltip.style.margin = '0';
tooltip.style.top = `${rect.bottom + 8}px`;
} else {
tooltip.style.position = 'absolute';
tooltip.style.left = '50%';
tooltip.style.right = 'auto';
tooltip.style.transform = 'translateX(-50%)';
tooltip.style.margin = '8px 0 0 0';
tooltip.style.top = '100%';
}
});
2025-04-28 12:25:20 +08:00
}
handleDropdownAction(action) {
switch(action) {
case 'profile':
this.dispatchEvent(new CustomEvent('menu-action', {
detail: { action: 'profile' },
bubbles: true,
composed: true
}));
break;
case 'users':
this.dispatchEvent(new CustomEvent('menu-action', {
detail: { action: 'users' },
bubbles: true,
composed: true
}));
break;
case 'logout':
this.logout();
break;
}
}
async checkLoginStatus() {
2025-04-28 12:25:20 +08:00
try {
const response = await fetch('/api/check-auth', {
credentials: 'include'
});
const result = await response.json();
if (result.success) {
this.updateUserInfo(result.user);
} else {
document.getElementById('authContainer').style.display = 'block';
document.getElementById('mainContainer').style.display = 'none';
2025-04-28 12:25:20 +08:00
}
} catch (error) {
console.error('获取用户信息失败:', error);
document.getElementById('authContainer').style.display = 'block';
document.getElementById('mainContainer').style.display = 'none';
2025-04-28 12:25:20 +08:00
}
}
updateUserInfo(userInfo) {
// 设置用户名
this.shadowRoot.getElementById('userName').textContent = userInfo.username;
// 设置用户图标和tooltip信息
const userIcon = this.shadowRoot.getElementById('userIcon');
2025-04-28 12:25:20 +08:00
const userTooltip = this.shadowRoot.getElementById('userTooltip');
// 使用用户自定义图标
if (userInfo.icon && userInfo.icon.trim() !== '') {
const base64Data = userInfo.icon.trim();
// 检查Base64数据是否有效且看起来像图片数据
if (base64Data.length > 100 &&
/^[A-Za-z0-9+/]*={0,2}$/.test(base64Data)) {
// 根据Base64数据开头判断图片格式
let mimeType = 'image/jpeg'; // 默认
if (base64Data.startsWith('iVBORw0KGgo')) {
mimeType = 'image/png';
} else if (base64Data.startsWith('R0lGODlh')) {
mimeType = 'image/gif';
} else if (base64Data.startsWith('UklGR')) {
mimeType = 'image/webp';
}
userIcon.src = `data:${mimeType};base64,${base64Data}`;
} else {
console.warn('无效的图片Base64数据格式使用默认头像');
userIcon.src = 'assets/icons/png/user_b.png';
}
} else {
// 如果没有自定义图标,使用默认用户图标
userIcon.src = 'assets/icons/png/user_b.png';
2025-04-28 12:25:20 +08:00
}
userIcon.alt = userInfo.username;
2025-04-28 12:25:20 +08:00
// 添加图片加载错误处理
userIcon.onerror = () => {
console.warn('用户头像加载失败,使用默认头像');
console.warn('尝试加载的URL:', userIcon.src);
userIcon.src = 'assets/icons/png/user_b.png';
};
2025-04-28 12:25:20 +08:00
// 构建HTML内容
const tooltipContent = `
<div class="info-row">
<span class="label">用户名</span>
<span class="value">${userInfo.username || '未设置'}</span>
</div>
<div class="info-row">
<span class="label">真实姓名</span>
<span class="value">${userInfo.full_name || '未设置'}</span>
</div>
<div class="info-row">
<span class="label">权限级别</span>
<span class="value">${this.getAccessLevelName(userInfo.access_level)}</span>
</div>
<div class="info-row">
<span class="label">所属部门</span>
<span class="value">${userInfo.department || '未设置'}</span>
</div>
<div class="info-row">
<span class="label">职位</span>
<span class="value">${userInfo.position || '未设置'}</span>
</div>
<div class="info-row">
<span class="label">电子邮箱</span>
<span class="value">${userInfo.email || '未设置'}</span>
</div>
<div class="info-row">
<span class="label">联系电话</span>
<span class="value">${userInfo.phone || '未设置'}</span>
</div>
2025-04-28 12:25:20 +08:00
`;
userTooltip.innerHTML = tooltipContent;
// 控制用户管理选项的显示
const userManagementItem = this.shadowRoot.querySelector('.admin-only');
if (userManagementItem) {
userManagementItem.style.display = parseInt(userInfo.access_level) >= 3 ? 'flex' : 'none';
}
}
getAccessLevelName(accessLevel) {
const level = parseInt(accessLevel);
switch(level) {
case 1: return '普通用户';
case 2: return '开发者';
case 3: return '组长';
case 4: return '管理员';
default: return '访客';
2025-04-28 12:25:20 +08:00
}
}
async logout() {
try {
const response = await fetch('/api/logout', {
method: 'POST',
credentials: 'include'
});
if (response.ok) {
// 获取认证容器和主容器
const authContainer = document.getElementById('authContainer');
const mainContainer = document.getElementById('mainContainer');
// 先隐藏主容器
mainContainer.classList.remove('visible');
// 等待过渡效果完成
setTimeout(() => {
location.reload();
mainContainer.style.display = 'none';
// 显示认证容器
authContainer.style.display = 'block';
// 等待一帧以确保display:block生效
requestAnimationFrame(() => {
authContainer.classList.add('visible');
// 重置认证组件
const authComponent = document.querySelector('auth-component');
if (authComponent) {
authComponent.reset();
}
});
}, 300);
} else {
console.error('登出失败');
}
} catch (error) {
console.error('登出错误:', error);
}
2025-04-28 12:25:20 +08:00
}
}
customElements.define('user-info', UserInfo);