diff --git a/Login/login.cpp b/Login/login.cpp index 2d18852..0a7e92a 100644 --- a/Login/login.cpp +++ b/Login/login.cpp @@ -15,6 +15,8 @@ #include #include #include +#include +#include using json = nlohmann::json; namespace fs = std::filesystem; @@ -52,35 +54,49 @@ std::string encryptPassword(const std::string &password, const std::string &salt // Base64编码函数 std::string base64_encode(const unsigned char *data, size_t length) { - const char *base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - std::string ret; - ret.reserve(((length + 2) / 3) * 4); + BIO *bio, *b64; + BUF_MEM *bufferPtr; - for (size_t i = 0; i < length; i += 3) { - unsigned char octet_a = i < length ? data[i] : 0; - unsigned char octet_b = i + 1 < length ? data[i + 1] : 0; - unsigned char octet_c = i + 2 < length ? data[i + 2] : 0; + b64 = BIO_new(BIO_f_base64()); + bio = BIO_new(BIO_s_mem()); + bio = BIO_push(b64, bio); - unsigned char triple = (octet_a << 16) + (octet_b << 8) + octet_c; + BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); // 不添加换行符 + BIO_write(bio, data, length); + BIO_flush(bio); + BIO_get_mem_ptr(bio, &bufferPtr); - ret.push_back(base64_chars[(triple >> 18) & 0x3F]); - ret.push_back(base64_chars[(triple >> 12) & 0x3F]); - ret.push_back(base64_chars[(triple >> 6) & 0x3F]); - ret.push_back(base64_chars[triple & 0x3F]); + std::string result(bufferPtr->data, bufferPtr->length); + BIO_free_all(bio); + + return result; +} + +// Base64解码函数 +std::vector base64_decode(const std::string &encoded_string) +{ + BIO *bio, *b64; + std::vector result; + + b64 = BIO_new(BIO_f_base64()); + bio = BIO_new_mem_buf(encoded_string.c_str(), encoded_string.length()); + bio = BIO_push(b64, bio); + + BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); // 不添加换行符 + + // 计算解码后的长度 + size_t decoded_length = (encoded_string.length() * 3) / 4; + result.resize(decoded_length); + + int actual_length = BIO_read(bio, result.data(), decoded_length); + if (actual_length > 0) { + result.resize(actual_length); + } else { + result.clear(); } - // 添加填充 - switch (length % 3) { - case 1: - ret[ret.size() - 2] = '='; - ret[ret.size() - 1] = '='; - break; - case 2: - ret[ret.size() - 1] = '='; - break; - } - - return ret; + BIO_free_all(bio); + return result; } // 利用用户名和固定密钥派生32字节AES密钥 @@ -910,4 +926,94 @@ extern "C" LOGIN_EXPORT int deleteUser(int user_id) } catch (const std::exception &) { return -1; } +} + +// 更新用户头像 +extern "C" LOGIN_EXPORT int updateUserIcon(int user_id, const char *icon_base64_buffer, + int icon_base64_length) +{ + try { + if (!icon_base64_buffer || icon_base64_length <= 0) { + return -1; // 无效的输入参数 + } + + std::string icon_base64(icon_base64_buffer, icon_base64_length); + if (icon_base64.empty()) { + return -1; // 空的Base64数据 + } + + const char *xnCorePath = std::getenv("XNCore"); + if (!xnCorePath) { + return -2; // 环境变量未设置 + } + + fs::path dbPath = fs::path(xnCorePath) / "database" / "UserInfo.db"; + + { + sqlite3 *db; + if (sqlite3_open(dbPath.string().c_str(), &db) != SQLITE_OK) { + return -3; // 数据库连接失败 + } + + // 首先检查用户是否存在 + sqlite3_stmt *stmt; + const char *checkQueryStr = "SELECT id FROM users WHERE id = ?"; + + if (sqlite3_prepare_v2(db, checkQueryStr, -1, &stmt, nullptr) == SQLITE_OK) { + sqlite3_bind_int(stmt, 1, user_id); + + if (sqlite3_step(stmt) != SQLITE_ROW) { + sqlite3_finalize(stmt); + sqlite3_close(db); + return -4; // 用户不存在 + } + sqlite3_finalize(stmt); + } + + // 将Base64字符串转换为二进制数据 + std::vector icon_data; + try { + icon_data = base64_decode(icon_base64); + } catch (...) { + sqlite3_close(db); + return -5; // Base64解码失败 + } + + if (icon_data.empty()) { + sqlite3_close(db); + return -6; // 解码后数据为空 + } + + // 更新用户头像 + stmt = nullptr; + const char *queryStr = "UPDATE users SET icon = ? WHERE id = ?"; + + if (sqlite3_prepare_v2(db, queryStr, -1, &stmt, nullptr) == SQLITE_OK) { + sqlite3_bind_blob(stmt, 1, icon_data.data(), icon_data.size(), SQLITE_TRANSIENT); + sqlite3_bind_int(stmt, 2, user_id); + + if (sqlite3_step(stmt) == SQLITE_DONE) { + sqlite3_finalize(stmt); + sqlite3_close(db); + return 0; // 更新成功 + } else { + // 获取具体的错误信息 + int error_code = sqlite3_errcode(db); + const char *error_msg = sqlite3_errmsg(db); + std::cout << "UPDATE icon failed with error code: " << error_code + << ", message: " << (error_msg ? error_msg : "unknown") << std::endl; + sqlite3_finalize(stmt); + sqlite3_close(db); + return -7; // UPDATE语句执行失败 + } + } else { + sqlite3_close(db); + return -8; // UPDATE语句准备失败 + } + } + + return -9; // 其他错误 + } catch (const std::exception &) { + return -10; // 异常错误 + } } \ No newline at end of file diff --git a/Release/database/UserInfo.db b/Release/database/UserInfo.db index 512d0d6..2b0b402 100644 Binary files a/Release/database/UserInfo.db and b/Release/database/UserInfo.db differ diff --git a/Release/database/XNSim.db b/Release/database/XNSim.db index 7bfd059..5fae359 100644 Binary files a/Release/database/XNSim.db and b/Release/database/XNSim.db differ diff --git a/XNSimHtml/assets/icons/png/cancel.png b/XNSimHtml/assets/icons/png/cancel.png new file mode 100644 index 0000000..9862b73 Binary files /dev/null and b/XNSimHtml/assets/icons/png/cancel.png differ diff --git a/XNSimHtml/assets/icons/png/mail.png b/XNSimHtml/assets/icons/png/mail.png new file mode 100644 index 0000000..ca3fe65 Binary files /dev/null and b/XNSimHtml/assets/icons/png/mail.png differ diff --git a/XNSimHtml/components/profile-center.js b/XNSimHtml/components/profile-center.js index 1e00315..56e57f5 100644 --- a/XNSimHtml/components/profile-center.js +++ b/XNSimHtml/components/profile-center.js @@ -2,10 +2,33 @@ class ProfileCenter extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); + this.userInfo = null; + this.isEditing = false; } connectedCallback() { this.render(); + this.loadUserInfo(); + this.addEventListeners(); + } + + async loadUserInfo() { + try { + const response = await fetch('/api/check-auth', { + credentials: 'include' + }); + const result = await response.json(); + + if (result.success) { + this.userInfo = result.user; + this.updateDisplay(); + } else { + this.showError('获取用户信息失败'); + } + } catch (error) { + console.error('获取用户信息失败:', error); + this.showError('获取用户信息失败'); + } } render() { @@ -20,37 +43,713 @@ class ProfileCenter extends HTMLElement { } .profile-container { - background-color: white; - border-radius: 8px; - box-shadow: 0 2px 8px rgba(0,0,0,0.1); - padding: 16px; - height: 100%; - box-sizing: border-box; + max-width: 800px; + margin: 0 auto; + background: white; + border-radius: 12px; + box-shadow: 0 4px 20px rgba(0,0,0,0.1); + overflow: hidden; } .profile-header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 30px 24px; + text-align: center; + position: relative; + } + + .avatar-section { + margin-bottom: 15px; + position: relative; + display: inline-block; + } + + .avatar { + width: 100px; + height: 100px; + border-radius: 50%; + border: 3px solid rgba(255,255,255,0.3); + object-fit: cover; + margin: 0 auto 12px; + display: block; + background: rgba(255,255,255,0.1); + } + + .avatar-edit-btn { + position: absolute; + bottom: 15px; + right: 0; + width: 32px; + height: 32px; + border-radius: 50%; + background: rgba(255,255,255,0.9); + border: 2px solid #667eea; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s ease; + box-shadow: 0 2px 8px rgba(0,0,0,0.2); + } + + .avatar-edit-btn:hover { + background: white; + transform: scale(1.1); + box-shadow: 0 4px 12px rgba(0,0,0,0.3); + } + + .avatar-edit-btn img { + width: 16px; + height: 16px; + opacity: 0.7; + } + + .avatar-edit-btn:hover img { + opacity: 1; + } + + .file-input { + display: none; + } + + .upload-loading { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(0,0,0,0.7); + color: white; + padding: 8px 12px; + border-radius: 6px; + font-size: 12px; + z-index: 10; + } + + .username { + font-size: 24px; + font-weight: 600; + margin: 0 0 6px 0; + } + + .user-role { + font-size: 14px; + opacity: 0.9; + margin: 0; + } + + .profile-content { + padding: 24px; + } + + .section-title { + font-size: 18px; + font-weight: 600; + color: #333; + margin: 0 0 16px 0; + display: flex; + align-items: center; + gap: 8px; + } + + .section-title img { + width: 18px; + height: 18px; + } + + .info-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 16px; + margin-bottom: 24px; + } + + .info-card { + background: #f8f9fa; + border-radius: 10px; + padding: 16px; + border: 1px solid #e9ecef; + } + + .info-item { display: flex; justify-content: space-between; align-items: center; - margin-bottom: 20px; - padding-bottom: 10px; - border-bottom: 1px solid #e0e0e0; + padding: 10px 0; + border-bottom: 1px solid #e9ecef; } - .profile-title { - font-size: 18px; - font-weight: bold; + .info-item:last-child { + border-bottom: none; + } + + .info-label { + font-weight: 500; + color: #6c757d; + min-width: 90px; + } + + .info-value { color: #333; + text-align: right; + flex: 1; + margin-left: 16px; + } + + .info-value.empty { + color: #adb5bd; + font-style: italic; + } + + .edit-input { + width: 100%; + padding: 8px 12px; + border: 1px solid #ced4da; + border-radius: 6px; + font-size: 14px; + background: white; + } + + .edit-input:focus { + outline: none; + border-color: #667eea; + box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1); + } + + .action-buttons { + display: flex; + gap: 12px; + justify-content: center; + margin-top: 24px; + } + + .btn { + padding: 10px 20px; + border: none; + border-radius: 6px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + display: flex; + align-items: center; + gap: 6px; + } + + .btn-primary { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + } + + .btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3); + } + + .btn-secondary { + background: #6c757d; + color: white; + } + + .btn-secondary:hover { + background: #5a6268; + transform: translateY(-2px); + } + + .btn img { + width: 16px; + height: 16px; + } + + .permission-badge { + display: inline-block; + padding: 4px 12px; + border-radius: 20px; + font-size: 12px; + font-weight: 500; + text-transform: uppercase; + } + + .permission-admin { + background: #dc3545; + color: white; + } + + .permission-leader { + background: #fd7e14; + color: white; + } + + .permission-developer { + background: #17a2b8; + color: white; + } + + .permission-user { + background: #28a745; + color: white; + } + + .permission-guest { + background: #6c757d; + color: white; + } + + .error-message { + background: #f8d7da; + color: #721c24; + padding: 15px; + border-radius: 8px; + margin: 20px 0; + text-align: center; + } + + .success-message { + background: #d4edda; + color: #155724; + padding: 15px; + border-radius: 8px; + margin: 20px 0; + text-align: center; + } + + .loading { + text-align: center; + padding: 40px; + color: #6c757d; + } + + @media (max-width: 768px) { + :host { + padding: 10px; + } + + .profile-container { + margin: 0; + } + + .info-grid { + grid-template-columns: 1fr; + } + + .action-buttons { + flex-direction: column; + } }
-
个人中心
+
+ 用户头像 +
+ 编辑头像 +
+ + +

加载中...

+

加载中...

+
+
+
+ + + +
+ 基本信息 + 基本信息 +
+ +
+
+
+ 用户名 + - +
+
+ 真实姓名 + - +
+
+ 权限级别 + - +
+
+ +
+
+ 所属部门 + - +
+
+ 职位 + - +
+
+ 用户ID + - +
+
+
+ +
+ 联系方式 + 联系方式 +
+ +
+
+
+ 电子邮箱 + - +
+
+ 联系电话 + - +
+
+
+ +
+ + + +
-
个人中心组件内容(待实现)
`; } + + updateDisplay() { + if (!this.userInfo) return; + + // 更新头像 + const avatar = this.shadowRoot.getElementById('userAvatar'); + if (this.userInfo.icon && this.userInfo.icon.trim() !== '') { + const base64Data = this.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'; + } + + avatar.src = `data:${mimeType};base64,${base64Data}`; + } else { + avatar.src = 'assets/icons/png/user.png'; + } + } else { + avatar.src = 'assets/icons/png/user.png'; + } + + // 更新基本信息 + this.shadowRoot.getElementById('userName').textContent = this.userInfo.username || '未设置'; + this.shadowRoot.getElementById('userRole').textContent = this.getAccessLevelName(this.userInfo.access_level); + + // 更新详细信息 + this.updateInfoField('username', this.userInfo.username); + this.updateInfoField('fullName', this.userInfo.full_name); + this.updateInfoField('accessLevel', this.getAccessLevelName(this.userInfo.access_level), true); + this.updateInfoField('department', this.userInfo.department); + this.updateInfoField('position', this.userInfo.position); + this.updateInfoField('userId', this.userInfo.id); + this.updateInfoField('email', this.userInfo.email); + this.updateInfoField('phone', this.userInfo.phone); + } + + updateInfoField(fieldId, value, isBadge = false) { + const element = this.shadowRoot.getElementById(fieldId); + if (element) { + if (isBadge) { + element.innerHTML = `${value}`; + } else { + if (value && value !== '') { + element.textContent = value; + element.classList.remove('empty'); + } else { + element.textContent = '未设置'; + element.classList.add('empty'); + } + } + } + } + + getAccessLevelName(accessLevel) { + const level = parseInt(accessLevel); + switch(level) { + case 1: return '普通用户'; + case 2: return '开发者'; + case 3: return '组长'; + case 4: return '管理员'; + default: return '访客'; + } + } + + getPermissionClass(accessLevel) { + const level = parseInt(accessLevel); + switch(level) { + case 1: return 'user'; + case 2: return 'developer'; + case 3: return 'leader'; + case 4: return 'admin'; + default: return 'guest'; + } + } + + addEventListeners() { + const editBtn = this.shadowRoot.getElementById('editBtn'); + const saveBtn = this.shadowRoot.getElementById('saveBtn'); + const cancelBtn = this.shadowRoot.getElementById('cancelBtn'); + const avatarEditBtn = this.shadowRoot.getElementById('avatarEditBtn'); + const avatarFileInput = this.shadowRoot.getElementById('avatarFileInput'); + + editBtn.addEventListener('click', () => this.startEditing()); + saveBtn.addEventListener('click', () => this.saveChanges()); + cancelBtn.addEventListener('click', () => this.cancelEditing()); + + // 头像编辑按钮点击事件 + avatarEditBtn.addEventListener('click', () => { + avatarFileInput.click(); + }); + + // 文件选择事件 + avatarFileInput.addEventListener('change', (e) => { + const file = e.target.files[0]; + if (file) { + this.handleAvatarUpload(file); + } + }); + } + + startEditing() { + this.isEditing = true; + this.shadowRoot.getElementById('editBtn').style.display = 'none'; + this.shadowRoot.getElementById('saveBtn').style.display = 'flex'; + this.shadowRoot.getElementById('cancelBtn').style.display = 'flex'; + + // 将可编辑字段转换为输入框 + this.convertToInput('fullName', this.userInfo.full_name || ''); + this.convertToInput('department', this.userInfo.department || ''); + this.convertToInput('position', this.userInfo.position || ''); + this.convertToInput('email', this.userInfo.email || ''); + this.convertToInput('phone', this.userInfo.phone || ''); + } + + convertToInput(fieldId, value) { + const element = this.shadowRoot.getElementById(fieldId); + if (element) { + const input = document.createElement('input'); + input.type = 'text'; + input.className = 'edit-input'; + input.value = value; + input.id = `${fieldId}Input`; + element.innerHTML = ''; + element.appendChild(input); + } + } + + async saveChanges() { + const updatedInfo = { + full_name: this.shadowRoot.getElementById('fullNameInput')?.value || '', + department: this.shadowRoot.getElementById('departmentInput')?.value || '', + position: this.shadowRoot.getElementById('positionInput')?.value || '', + email: this.shadowRoot.getElementById('emailInput')?.value || '', + phone: this.shadowRoot.getElementById('phoneInput')?.value || '' + }; + + try { + const response = await fetch('/api/update-user-info', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + userId: this.userInfo.id, + userInfo: updatedInfo + }), + credentials: 'include' + }); + + const result = await response.json(); + + if (result.success) { + // 更新本地用户信息 + this.userInfo = { ...this.userInfo, ...updatedInfo }; + this.showSuccess('用户信息更新成功'); + this.cancelEditing(); + this.updateDisplay(); + } else { + this.showError('更新失败: ' + result.message); + } + } catch (error) { + console.error('更新用户信息失败:', error); + this.showError('更新用户信息失败'); + } + } + + cancelEditing() { + this.isEditing = false; + this.shadowRoot.getElementById('editBtn').style.display = 'flex'; + this.shadowRoot.getElementById('saveBtn').style.display = 'none'; + this.shadowRoot.getElementById('cancelBtn').style.display = 'none'; + + // 恢复显示模式 + this.updateDisplay(); + } + + showError(message) { + const errorElement = this.shadowRoot.getElementById('errorMessage'); + errorElement.textContent = message; + errorElement.style.display = 'block'; + setTimeout(() => { + errorElement.style.display = 'none'; + }, 5000); + } + + showSuccess(message) { + const successElement = this.shadowRoot.getElementById('successMessage'); + successElement.textContent = message; + successElement.style.display = 'block'; + setTimeout(() => { + successElement.style.display = 'none'; + }, 3000); + } + + async handleAvatarUpload(file) { + // 验证文件类型 + if (!file.type.startsWith('image/')) { + this.showError('请选择图片文件'); + return; + } + + // 验证文件大小(限制为500KB) + if (file.size > 500 * 1024) { + this.showError('图片大小不能超过500KB'); + return; + } + + try { + // 显示上传中状态 + const loadingElement = this.shadowRoot.getElementById('uploadLoading'); + loadingElement.style.display = 'block'; + + // 压缩图片并转换为Base64 + const base64Data = await this.compressAndConvertToBase64(file); + + // 提取纯Base64数据(去掉data URL前缀) + const pureBase64 = base64Data.replace(/^data:image\/[a-z]+;base64,/, ''); + + // 调用后端API更新头像 + const response = await fetch('/api/update-user-icon', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + userId: this.userInfo.id, + iconBase64: pureBase64 + }), + credentials: 'include' + }); + + const result = await response.json(); + + if (result.success) { + // 更新本地头像显示 + const avatar = this.shadowRoot.getElementById('userAvatar'); + avatar.src = base64Data; + + // 更新用户信息中的头像 + this.userInfo.icon = base64Data; + + this.showSuccess('头像更新成功'); + } else { + this.showError('头像更新失败: ' + (result.error || result.message)); + } + } catch (error) { + console.error('头像上传失败:', error); + this.showError('头像上传失败'); + } finally { + // 隐藏上传中状态 + const loadingElement = this.shadowRoot.getElementById('uploadLoading'); + loadingElement.style.display = 'none'; + + // 清空文件输入框 + this.shadowRoot.getElementById('avatarFileInput').value = ''; + } + } + + compressAndConvertToBase64(file) { + return new Promise((resolve, reject) => { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + const img = new Image(); + + img.onload = () => { + // 设置画布尺寸(限制最大尺寸为200x200) + const maxSize = 200; + let { width, height } = img; + + if (width > height) { + if (width > maxSize) { + height = (height * maxSize) / width; + width = maxSize; + } + } else { + if (height > maxSize) { + width = (width * maxSize) / height; + height = maxSize; + } + } + + canvas.width = width; + canvas.height = height; + + // 清空画布并设置透明背景 + ctx.clearRect(0, 0, width, height); + + // 绘制图片到画布 + ctx.drawImage(img, 0, 0, width, height); + + // 根据原图片格式选择合适的输出格式 + let mimeType = 'image/png'; // 默认使用PNG保持透明背景 + let quality = 0.9; + + // 如果原图是JPEG格式,则输出JPEG + if (file.type === 'image/jpeg' || file.type === 'image/jpg') { + mimeType = 'image/jpeg'; + quality = 0.8; + } + + // 转换为Base64 + const base64 = canvas.toDataURL(mimeType, quality); + resolve(base64); + }; + + img.onerror = () => { + reject(new Error('图片加载失败')); + }; + + // 从文件创建URL + const url = URL.createObjectURL(file); + img.src = url; + }); + } } customElements.define('profile-center', ProfileCenter); \ No newline at end of file diff --git a/XNSimHtml/components/user-info.js b/XNSimHtml/components/user-info.js index 99f8dff..8ef894d 100644 --- a/XNSimHtml/components/user-info.js +++ b/XNSimHtml/components/user-info.js @@ -326,14 +326,41 @@ class UserInfo extends HTMLElement { const userTooltip = this.shadowRoot.getElementById('userTooltip'); // 使用用户自定义图标 - if (userInfo.icon) { - userIcon.src = userInfo.icon; + 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 = `
diff --git a/XNSimHtml/routes/auth.js b/XNSimHtml/routes/auth.js index 29f7961..057a0a4 100644 --- a/XNSimHtml/routes/auth.js +++ b/XNSimHtml/routes/auth.js @@ -1,6 +1,6 @@ const express = require('express'); const router = express.Router(); -const { loginLib, stringToBuffer } = require('../utils/xnCoreService'); +const { loginLib, stringToBuffer, updateUserIcon } = require('../utils/xnCoreService'); // 登录API路由 router.post('/login', (req, res) => { @@ -28,7 +28,7 @@ router.post('/login', (req, res) => { if (userId > 0) { try { - const userInfoBuffer = Buffer.alloc(1024); + const userInfoBuffer = Buffer.alloc(1024 * 1024); // 增加到1MB以容纳头像数据 const userInfoState = loginLib.getUserInfo(userId, userInfoBuffer, userInfoBuffer.length); if (userInfoState === 0) { @@ -392,7 +392,7 @@ router.get('/user-info/:userId', (req, res) => { throw new Error('动态库未正确加载'); } - const userInfoBuffer = Buffer.alloc(1024); + const userInfoBuffer = Buffer.alloc(1024 * 1024); // 增加到1MB以容纳头像数据 const result = loginLib.getUserInfo(parseInt(userId), userInfoBuffer, userInfoBuffer.length); if (result === 0) { @@ -416,4 +416,26 @@ router.get('/user-info/:userId', (req, res) => { } }); +// 更新用户头像路由 +router.post('/update-user-icon', (req, res) => { + const { userId, iconBase64 } = req.body; + + if (!userId || !iconBase64) { + return res.status(400).json({ success: false, message: '用户ID和头像数据不能为空' }); + } + + try { + const result = updateUserIcon(parseInt(userId), iconBase64); + + if (result === '头像更新成功') { + res.json({ success: true, message: '头像更新成功' }); + } else { + res.status(400).json({ success: false, message: '头像更新失败', error: result }); + } + } catch (error) { + console.error('更新用户头像过程出错:', error); + res.status(500).json({ success: false, message: '服务器内部错误', error: error.message }); + } +}); + module.exports = router; \ No newline at end of file diff --git a/XNSimHtml/utils/xnCoreService.js b/XNSimHtml/utils/xnCoreService.js index eb1bb5d..17fa21e 100644 --- a/XNSimHtml/utils/xnCoreService.js +++ b/XNSimHtml/utils/xnCoreService.js @@ -37,6 +37,7 @@ try { 'changePassword': ['int', ['int', StringType, 'int', StringType, 'int']], 'updateUserInfo': ['int', ['int', StringType, 'int']], 'updateUserAccessLevel': ['int', ['int', 'int']], + 'updateUserIcon': ['int', ['int', StringType, 'int']], 'getAllUsersSimpleInfo': ['int', [StringType, 'int']], 'resetPassword': ['int', ['int']], 'deleteUser': ['int', ['int']] @@ -602,6 +603,24 @@ function deleteUser(userId) { } } +// 更新用户头像 +function updateUserIcon(userId, iconBase64) { + if (!loginLib) { + return '登录库未加载'; + } + try { + const iconBuffer = stringToBuffer(iconBase64); + const result = loginLib.updateUserIcon(userId, iconBuffer.buffer, iconBuffer.length); + if (result === 0) { + return '头像更新成功'; + } else { + return `头像更新失败,错误码:${result}`; + } + } catch (error) { + return `头像更新失败: ${error.message}`; + } +} + module.exports = { loginLib, monitorLib, @@ -633,5 +652,6 @@ module.exports = { getCollectDataStatus, stopCollectData, resetPassword, - deleteUser + deleteUser, + updateUserIcon }; \ No newline at end of file