V0.24.0.250617_alpha:登录相关功能修改
This commit is contained in:
parent
ae419e5b83
commit
3af9774439
149
Login/login.cpp
149
Login/login.cpp
@ -14,6 +14,7 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
using json = nlohmann::json;
|
||||
namespace fs = std::filesystem;
|
||||
@ -82,6 +83,94 @@ std::string base64_encode(const unsigned char *data, size_t length)
|
||||
return ret;
|
||||
}
|
||||
|
||||
// 利用用户名和固定密钥派生32字节AES密钥
|
||||
std::vector<unsigned char> derive_aes_key(const std::string &username, const std::string &fixed_key)
|
||||
{
|
||||
unsigned char hash[SHA256_DIGEST_LENGTH];
|
||||
std::string key_material = username + fixed_key;
|
||||
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
|
||||
EVP_DigestInit_ex(ctx, EVP_sha256(), NULL);
|
||||
EVP_DigestUpdate(ctx, key_material.c_str(), key_material.length());
|
||||
EVP_DigestFinal_ex(ctx, hash, NULL);
|
||||
EVP_MD_CTX_free(ctx);
|
||||
return std::vector<unsigned char>(hash, hash + SHA256_DIGEST_LENGTH);
|
||||
}
|
||||
|
||||
// AES-256-CBC加密
|
||||
std::vector<unsigned char> aes_encrypt(const std::string &plaintext,
|
||||
const std::vector<unsigned char> &key,
|
||||
std::vector<unsigned char> &iv_out)
|
||||
{
|
||||
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
|
||||
if (!ctx)
|
||||
throw std::runtime_error("EVP_CIPHER_CTX_new failed");
|
||||
iv_out.resize(16);
|
||||
if (!RAND_bytes(iv_out.data(), iv_out.size())) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
throw std::runtime_error("RAND_bytes failed");
|
||||
}
|
||||
if (1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key.data(), iv_out.data()))
|
||||
throw std::runtime_error("EVP_EncryptInit_ex failed");
|
||||
std::vector<unsigned char> ciphertext(plaintext.size() + 16);
|
||||
int len = 0, ciphertext_len = 0;
|
||||
if (1
|
||||
!= EVP_EncryptUpdate(ctx, ciphertext.data(), &len,
|
||||
reinterpret_cast<const unsigned char *>(plaintext.c_str()),
|
||||
plaintext.size()))
|
||||
throw std::runtime_error("EVP_EncryptUpdate failed");
|
||||
ciphertext_len = len;
|
||||
if (1 != EVP_EncryptFinal_ex(ctx, ciphertext.data() + len, &len))
|
||||
throw std::runtime_error("EVP_EncryptFinal_ex failed");
|
||||
ciphertext_len += len;
|
||||
ciphertext.resize(ciphertext_len);
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
// AES-256-CBC解密
|
||||
std::string aes_decrypt(const std::vector<unsigned char> &ciphertext,
|
||||
const std::vector<unsigned char> &key, const std::vector<unsigned char> &iv)
|
||||
{
|
||||
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
|
||||
if (!ctx)
|
||||
throw std::runtime_error("EVP_CIPHER_CTX_new failed");
|
||||
if (1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key.data(), iv.data()))
|
||||
throw std::runtime_error("EVP_DecryptInit_ex failed");
|
||||
std::vector<unsigned char> plaintext(ciphertext.size());
|
||||
int len = 0, plaintext_len = 0;
|
||||
if (1 != EVP_DecryptUpdate(ctx, plaintext.data(), &len, ciphertext.data(), ciphertext.size()))
|
||||
throw std::runtime_error("EVP_DecryptUpdate failed");
|
||||
plaintext_len = len;
|
||||
if (1 != EVP_DecryptFinal_ex(ctx, plaintext.data() + len, &len))
|
||||
throw std::runtime_error("EVP_DecryptFinal_ex failed");
|
||||
plaintext_len += len;
|
||||
plaintext.resize(plaintext_len);
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return std::string(plaintext.begin(), plaintext.end());
|
||||
}
|
||||
|
||||
// 二进制转16进制字符串
|
||||
std::string bin2hex(const std::vector<unsigned char> &data)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
for (auto c : data) {
|
||||
oss << std::hex << std::setw(2) << std::setfill('0') << (int)c;
|
||||
}
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
// 16进制字符串转二进制
|
||||
std::vector<unsigned char> hex2bin(const std::string &hex)
|
||||
{
|
||||
std::vector<unsigned char> out;
|
||||
for (size_t i = 0; i + 1 < hex.length(); i += 2) {
|
||||
std::string byteString = hex.substr(i, 2);
|
||||
unsigned char byte = (unsigned char)strtol(byteString.c_str(), nullptr, 16);
|
||||
out.push_back(byte);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
extern "C" LOGIN_EXPORT int validateUser(const char *username_buffer, size_t username_length,
|
||||
const char *password_buffer, size_t password_length)
|
||||
{
|
||||
@ -159,7 +248,27 @@ extern "C" LOGIN_EXPORT int getUserInfo(int user_id, char *result, int result_le
|
||||
json userInfo;
|
||||
userInfo["id"] = sqlite3_column_int(stmt, 0);
|
||||
userInfo["username"] = reinterpret_cast<const char *>(sqlite3_column_text(stmt, 1));
|
||||
userInfo["access_level"] = sqlite3_column_int(stmt, 3);
|
||||
// access_level 解密
|
||||
std::string accessLevelEnc =
|
||||
reinterpret_cast<const char *>(sqlite3_column_text(stmt, 3));
|
||||
std::vector<unsigned char> iv_and_ciphertext = hex2bin(accessLevelEnc);
|
||||
if (iv_and_ciphertext.size() < 16) {
|
||||
userInfo["access_level"] = nullptr;
|
||||
} else {
|
||||
std::vector<unsigned char> iv(iv_and_ciphertext.begin(),
|
||||
iv_and_ciphertext.begin() + 16);
|
||||
std::vector<unsigned char> ciphertext(iv_and_ciphertext.begin() + 16,
|
||||
iv_and_ciphertext.end());
|
||||
std::string fixed_key = "XNSim_Access_Key";
|
||||
std::string username = userInfo["username"].get<std::string>();
|
||||
std::vector<unsigned char> key = derive_aes_key(username, fixed_key);
|
||||
try {
|
||||
std::string accessLevelStr = aes_decrypt(ciphertext, key, iv);
|
||||
userInfo["access_level"] = std::stoi(accessLevelStr);
|
||||
} catch (...) {
|
||||
userInfo["access_level"] = nullptr;
|
||||
}
|
||||
}
|
||||
userInfo["full_name"] =
|
||||
reinterpret_cast<const char *>(sqlite3_column_text(stmt, 4));
|
||||
userInfo["phone"] = reinterpret_cast<const char *>(sqlite3_column_text(stmt, 5));
|
||||
@ -297,6 +406,17 @@ extern "C" LOGIN_EXPORT int registerUser(const char *username_buffer, int userna
|
||||
|
||||
// 验证权限级别
|
||||
int accessLevel = 0;
|
||||
if (userInfo.contains("access_level")) {
|
||||
accessLevel = userInfo["access_level"].get<int>();
|
||||
}
|
||||
// access_level 加密
|
||||
std::string fixed_key = "XNSim_Access_Key";
|
||||
std::vector<unsigned char> key = derive_aes_key(username_str, fixed_key);
|
||||
std::vector<unsigned char> iv;
|
||||
std::vector<unsigned char> ciphertext = aes_encrypt(std::to_string(accessLevel), key, iv);
|
||||
std::vector<unsigned char> iv_and_ciphertext = iv;
|
||||
iv_and_ciphertext.insert(iv_and_ciphertext.end(), ciphertext.begin(), ciphertext.end());
|
||||
std::string accessLevelEnc = bin2hex(iv_and_ciphertext);
|
||||
|
||||
// 生成加密密码
|
||||
std::string salt = generateSalt(username_str);
|
||||
@ -331,7 +451,7 @@ extern "C" LOGIN_EXPORT int registerUser(const char *username_buffer, int userna
|
||||
if (sqlite3_prepare_v2(db, queryStr, -1, &stmt, nullptr) == SQLITE_OK) {
|
||||
sqlite3_bind_text(stmt, 1, username_str.c_str(), -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 2, encryptedPassword.c_str(), -1, SQLITE_STATIC);
|
||||
sqlite3_bind_int(stmt, 3, accessLevel);
|
||||
sqlite3_bind_text(stmt, 3, accessLevelEnc.c_str(), -1, SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 4, userInfo["full_name"].get<std::string>().c_str(), -1,
|
||||
SQLITE_STATIC);
|
||||
sqlite3_bind_text(stmt, 5, userInfo["phone"].get<std::string>().c_str(), -1,
|
||||
@ -578,7 +698,30 @@ extern "C" LOGIN_EXPORT int updateUserAccessLevel(int user_id, int access_level)
|
||||
const char *queryStr = "UPDATE users SET access_level = ? WHERE id = ?";
|
||||
|
||||
if (sqlite3_prepare_v2(db, queryStr, -1, &stmt, nullptr) == SQLITE_OK) {
|
||||
sqlite3_bind_int(stmt, 1, access_level);
|
||||
// 需要查用户名
|
||||
std::string username;
|
||||
{
|
||||
sqlite3_stmt *stmt2;
|
||||
const char *q2 = "SELECT username FROM users WHERE id = ?";
|
||||
if (sqlite3_prepare_v2(db, q2, -1, &stmt2, nullptr) == SQLITE_OK) {
|
||||
sqlite3_bind_int(stmt2, 1, user_id);
|
||||
if (sqlite3_step(stmt2) == SQLITE_ROW) {
|
||||
username =
|
||||
reinterpret_cast<const char *>(sqlite3_column_text(stmt2, 0));
|
||||
}
|
||||
sqlite3_finalize(stmt2);
|
||||
}
|
||||
}
|
||||
std::string fixed_key = "XNSim_Access_Key";
|
||||
std::vector<unsigned char> key = derive_aes_key(username, fixed_key);
|
||||
std::vector<unsigned char> iv;
|
||||
std::vector<unsigned char> ciphertext =
|
||||
aes_encrypt(std::to_string(access_level), key, iv);
|
||||
std::vector<unsigned char> iv_and_ciphertext = iv;
|
||||
iv_and_ciphertext.insert(iv_and_ciphertext.end(), ciphertext.begin(),
|
||||
ciphertext.end());
|
||||
std::string accessLevelEnc = bin2hex(iv_and_ciphertext);
|
||||
sqlite3_bind_text(stmt, 1, accessLevelEnc.c_str(), -1, SQLITE_STATIC);
|
||||
sqlite3_bind_int(stmt, 2, user_id);
|
||||
|
||||
if (sqlite3_step(stmt) == SQLITE_DONE) {
|
||||
|
20
Login/test_encrypt/CMakeLists.txt
Normal file
20
Login/test_encrypt/CMakeLists.txt
Normal file
@ -0,0 +1,20 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(test_access_level_encrypt)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# 查找依赖包
|
||||
find_package(OpenSSL REQUIRED)
|
||||
find_package(SQLite3 REQUIRED)
|
||||
find_package(nlohmann_json 3.9.1 REQUIRED)
|
||||
|
||||
add_executable(test_access_level_encrypt test_access_level_encrypt.cpp)
|
||||
|
||||
# 包含上级目录头文件(login.cpp依赖)
|
||||
target_include_directories(test_access_level_encrypt PRIVATE ${CMAKE_SOURCE_DIR}/..)
|
||||
|
||||
# 链接OpenSSL
|
||||
if(OpenSSL_FOUND)
|
||||
target_link_libraries(test_access_level_encrypt PRIVATE OpenSSL::SSL OpenSSL::Crypto SQLite::SQLite3 nlohmann_json::nlohmann_json)
|
||||
endif()
|
36
Login/test_encrypt/test_access_level_encrypt.cpp
Normal file
36
Login/test_encrypt/test_access_level_encrypt.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include "../login.cpp" // 直接复用加密解密相关函数
|
||||
|
||||
int main()
|
||||
{
|
||||
std::string username = "test3";
|
||||
std::string fixed_key = "XNSim_Access_Key";
|
||||
std::vector<unsigned char> key = derive_aes_key(username, fixed_key);
|
||||
|
||||
for (int access_level = 0; access_level <= 4; ++access_level) {
|
||||
// 加密
|
||||
std::vector<unsigned char> iv;
|
||||
std::vector<unsigned char> ciphertext = aes_encrypt(std::to_string(access_level), key, iv);
|
||||
std::vector<unsigned char> iv_and_ciphertext = iv;
|
||||
iv_and_ciphertext.insert(iv_and_ciphertext.end(), ciphertext.begin(), ciphertext.end());
|
||||
std::string hexstr = bin2hex(iv_and_ciphertext);
|
||||
|
||||
// 解密
|
||||
std::vector<unsigned char> iv_and_ciphertext2 = hex2bin(hexstr);
|
||||
std::vector<unsigned char> iv2(iv_and_ciphertext2.begin(), iv_and_ciphertext2.begin() + 16);
|
||||
std::vector<unsigned char> ciphertext2(iv_and_ciphertext2.begin() + 16,
|
||||
iv_and_ciphertext2.end());
|
||||
std::string decrypted = aes_decrypt(ciphertext2, key, iv2);
|
||||
|
||||
std::cout << "username: " << username << std::endl;
|
||||
std::cout << "access_level: " << access_level << std::endl;
|
||||
std::cout << "加密后16进制: " << hexstr << std::endl;
|
||||
std::cout << "解密还原: " << decrypted << std::endl;
|
||||
std::cout << "-----------------------------" << std::endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
Binary file not shown.
@ -771,7 +771,7 @@ class RunSim extends HTMLElement {
|
||||
.list-content {
|
||||
overflow: auto;
|
||||
padding: 10px;
|
||||
max-height: 200px;
|
||||
max-height: 350px;
|
||||
}
|
||||
|
||||
.list-group {
|
||||
|
@ -52,7 +52,7 @@ class UserInfo extends HTMLElement {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.user-level-icon {
|
||||
.user-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 0;
|
||||
@ -60,10 +60,12 @@ class UserInfo extends HTMLElement {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.user-level-icon-wrapper {
|
||||
.user-icon-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
z-index: 1002;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.icon-small {
|
||||
@ -124,34 +126,52 @@ class UserInfo extends HTMLElement {
|
||||
position: absolute;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: white;
|
||||
padding: 12px 15px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
white-space: pre-line;
|
||||
font-size: 13px;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
margin-top: 10px;
|
||||
margin-top: 8px;
|
||||
z-index: 1001;
|
||||
min-width: 200px;
|
||||
text-align: left;
|
||||
line-height: 1.5;
|
||||
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: 4px 0;
|
||||
margin: 2px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tooltip .label {
|
||||
color: #a8c8ff;
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
min-width: 70px;
|
||||
}
|
||||
|
||||
.user-level-icon-wrapper:hover .tooltip {
|
||||
.tooltip .value {
|
||||
color: #ffffff;
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.user-icon-wrapper:hover .tooltip {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
@ -174,8 +194,8 @@ class UserInfo extends HTMLElement {
|
||||
</style>
|
||||
<div class="user-dropdown">
|
||||
<div class="user-dropdown-toggle">
|
||||
<div class="user-level-icon-wrapper">
|
||||
<img id="userLevelIcon" src="assets/icons/png/user.png" alt="用户等级" class="user-level-icon">
|
||||
<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>
|
||||
@ -203,6 +223,7 @@ class UserInfo extends HTMLElement {
|
||||
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) => {
|
||||
@ -225,6 +246,33 @@ class UserInfo extends HTMLElement {
|
||||
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) {
|
||||
@ -273,48 +321,49 @@ class UserInfo extends HTMLElement {
|
||||
// 设置用户名
|
||||
this.shadowRoot.getElementById('userName').textContent = userInfo.username;
|
||||
|
||||
// 设置用户等级图标和tooltip信息
|
||||
const userLevelIcon = this.shadowRoot.getElementById('userLevelIcon');
|
||||
// 设置用户图标和tooltip信息
|
||||
const userIcon = this.shadowRoot.getElementById('userIcon');
|
||||
const userTooltip = this.shadowRoot.getElementById('userTooltip');
|
||||
const accessLevel = parseInt(userInfo.access_level);
|
||||
|
||||
let levelName = '';
|
||||
switch(accessLevel) {
|
||||
case 1:
|
||||
userLevelIcon.src = 'assets/icons/png/user.png';
|
||||
userLevelIcon.alt = '普通用户';
|
||||
levelName = '普通用户';
|
||||
break;
|
||||
case 2:
|
||||
userLevelIcon.src = 'assets/icons/png/dvlp.png';
|
||||
userLevelIcon.alt = '开发者';
|
||||
levelName = '开发者';
|
||||
break;
|
||||
case 3:
|
||||
userLevelIcon.src = 'assets/icons/png/master.png';
|
||||
userLevelIcon.alt = '组长';
|
||||
levelName = '组长';
|
||||
break;
|
||||
case 4:
|
||||
userLevelIcon.src = 'assets/icons/png/admin.png';
|
||||
userLevelIcon.alt = '管理员';
|
||||
levelName = '管理员';
|
||||
break;
|
||||
default:
|
||||
userLevelIcon.src = 'assets/icons/png/guest.png';
|
||||
userLevelIcon.alt = '访客';
|
||||
levelName = '访客';
|
||||
// 使用用户自定义图标
|
||||
if (userInfo.icon) {
|
||||
userIcon.src = userInfo.icon;
|
||||
} else {
|
||||
// 如果没有自定义图标,使用默认用户图标
|
||||
userIcon.src = 'assets/icons/png/user_b.png';
|
||||
}
|
||||
userIcon.alt = userInfo.username;
|
||||
|
||||
// 构建HTML内容
|
||||
const tooltipContent = `
|
||||
<div class="info-row"><span class="label">用户名:</span>${userInfo.username}</div>
|
||||
<div class="info-row"><span class="label">真实姓名:</span>${userInfo.full_name || '未设置'}</div>
|
||||
<div class="info-row"><span class="label">权限级别:</span>${levelName}</div>
|
||||
<div class="info-row"><span class="label">所属部门:</span>${userInfo.department || '未设置'}</div>
|
||||
<div class="info-row"><span class="label">职位:</span>${userInfo.position || '未设置'}</div>
|
||||
<div class="info-row"><span class="label">电子邮箱:</span>${userInfo.email || '未设置'}</div>
|
||||
<div class="info-row"><span class="label">联系电话:</span>${userInfo.phone || '未设置'}</div>
|
||||
<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;
|
||||
@ -322,7 +371,18 @@ class UserInfo extends HTMLElement {
|
||||
// 控制用户管理选项的显示
|
||||
const userManagementItem = this.shadowRoot.querySelector('.admin-only');
|
||||
if (userManagementItem) {
|
||||
userManagementItem.style.display = accessLevel >= 3 ? 'flex' : 'none';
|
||||
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 '访客';
|
||||
}
|
||||
}
|
||||
|
||||
@ -342,6 +402,8 @@ class UserInfo extends HTMLElement {
|
||||
mainContainer.classList.remove('visible');
|
||||
// 等待过渡效果完成
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
|
||||
mainContainer.style.display = 'none';
|
||||
// 显示认证容器
|
||||
authContainer.style.display = 'block';
|
||||
|
@ -413,26 +413,6 @@
|
||||
fetch('/api/logout', {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
}).then(() => {
|
||||
// 清除所有标签页
|
||||
tabsContainer.clearAllTabs();
|
||||
|
||||
// 显示退出成功提示
|
||||
showToast('已安全退出登录');
|
||||
|
||||
// 隐藏主容器
|
||||
mainContainer.classList.remove('visible');
|
||||
setTimeout(() => {
|
||||
mainContainer.style.display = 'none';
|
||||
authContainer.style.display = 'block';
|
||||
requestAnimationFrame(() => {
|
||||
authContainer.classList.add('visible');
|
||||
const authComponent = document.querySelector('auth-component');
|
||||
if (authComponent) {
|
||||
authComponent.reset();
|
||||
}
|
||||
});
|
||||
}, 300);
|
||||
}).catch(error => {
|
||||
console.error('登出错误:', error);
|
||||
showToast('登出过程中发生错误');
|
||||
|
@ -39,6 +39,8 @@ router.post('/login', (req, res) => {
|
||||
|
||||
// 设置 session
|
||||
req.session.user = userInfo;
|
||||
|
||||
console.log('用户', userInfo.username, '登录成功,', '权限等级:', userInfo.access_level);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
|
Loading…
x
Reference in New Issue
Block a user