XNSim/XNSimHtml/components/user-info.js

456 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 {
width: 24px;
height: 24px;
margin-right: 0;
vertical-align: middle;
cursor: help;
}
.user-icon-wrapper {
position: relative;
display: inline-block;
z-index: 1002;
padding: 0;
margin: 0;
}
.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;
}
.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;
}
.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;
border-radius: 4px;
font-size: 13px;
visibility: hidden;
opacity: 0;
transition: opacity 0.3s;
top: 100%;
left: 50%;
transform: translateX(-50%);
margin-top: 8px;
z-index: 1001;
min-width: 200px;
text-align: left;
line-height: 1.4;
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;
}
.tooltip .info-row {
margin: 2px 0;
display: flex;
align-items: center;
gap: 8px;
white-space: nowrap;
}
.tooltip .label {
color: #a8c8ff;
flex-shrink: 0;
min-width: 70px;
}
.tooltip .value {
color: #ffffff;
flex: 1;
white-space: nowrap;
}
.user-icon-wrapper:hover .tooltip {
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;
}
</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">
<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');
// 点击切换下拉菜单
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%';
}
});
}
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() {
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';
}
} catch (error) {
console.error('获取用户信息失败:', error);
document.getElementById('authContainer').style.display = 'block';
document.getElementById('mainContainer').style.display = 'none';
}
}
updateUserInfo(userInfo) {
// 设置用户名
this.shadowRoot.getElementById('userName').textContent = userInfo.username;
// 设置用户图标和tooltip信息
const userIcon = this.shadowRoot.getElementById('userIcon');
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';
}
userIcon.alt = userInfo.username;
// 添加图片加载错误处理
userIcon.onerror = () => {
console.warn('用户头像加载失败,使用默认头像');
console.warn('尝试加载的URL:', userIcon.src);
userIcon.src = 'assets/icons/png/user_b.png';
};
// 构建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>
`;
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 '访客';
}
}
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);
}
}
}
customElements.define('user-info', UserInfo);