文件
2026-06-04 22:42:27 +08:00

1215 行
56 KiB
PHP

<?php
// 启用页面混淆和Gzip压缩
include 'minify.php';
// 启动会话
session_start();
// 引入模块
require_once 'modules/user.php';
require_once 'modules/message.php';
require_once 'modules/friend.php';
// 数据库初始化已在db.php中完成
// 自动登录:检查记住我令牌
autoLoginWithRememberToken();
// 每次访问都重新获取IP地址和地理位置
if (isLoggedIn()) {
$userId = $_SESSION['user_id'];
$db = Database::getInstance();
// 获取用户信息(包括头像)
$userResult = $db->query("SELECT * FROM users WHERE id = :user_id", ['user_id' => $userId]);
$currentUser = !empty($userResult) ? $userResult[0] : null;
// 直接获取用户IP地址
$userIp = getUserIP();
if ($userIp) {
// 调用IP地理位置API
$locationData = getIpLocation($userIp);
// 更新用户地理位置信息
if ($locationData && isset($locationData['status']) && $locationData['status'] === 'success') {
$db->execute("UPDATE users SET last_ip = :ip, country = :country, province = :province, city = :city WHERE id = :user_id", [
'ip' => $userIp,
'country' => $locationData['country'] ?? '',
'province' => $locationData['regionName'] ?? '',
'city' => $locationData['city'] ?? '',
'user_id' => $userId
]);
}
}
}
// 处理登出请求
if (isset($_GET['action']) && $_GET['action'] == 'logout') {
logout();
header('Location: index.php');
exit;
}
// 处理邮箱验证请求
if (isset($_GET['action']) && $_GET['action'] == 'verify_email' && isset($_GET['code'])) {
$verificationCode = $_GET['code'];
if (verifyEmail($verificationCode)) {
// 验证成功,重定向到首页以刷新用户状态
header('Location: index.php?success=邮箱验证成功!');
exit;
} else {
// 验证失败,重定向到首页显示错误信息
header('Location: index.php?error=邮箱验证失败,请检查验证链接是否正确。');
exit;
}
}
// 检查是否需要用户同意协议
function requireTermsAgreement() {
if (!isLoggedIn()) {
return 1; // 未登录用户默认需要同意
}
$userId = $_SESSION['user_id'];
$db = Database::getInstance();
$result = $db->query("SELECT getip FROM users WHERE id = :user_id", ['user_id' => $userId]);
return !empty($result) ? (int)$result[0]['getip'] : 1; // 默认开启
}
// 获取用户IP地址
function getUserIP() {
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
return $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
return $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
return $_SERVER['REMOTE_ADDR'] ?? '';
}
}
// 获取IP地理位置信息
function getIpLocation($ip) {
$url = "http://ip-api.com/json/{$ip}?lang=zh-CN";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_MAXREDIRS, 3);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
return false;
}
if ($httpCode !== 200) {
return false;
}
$data = json_decode($response, true);
if (!$data) {
return false;
}
if (!isset($data['status']) || $data['status'] !== 'success') {
return false;
}
return $data;
}
// 处理登录请求
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['login'])) {
// 验证是否同意协议
if (requireTermsAgreement() && (!isset($_POST['agree_terms']) || $_POST['agree_terms'] != 'on')) {
$error = '请阅读并同意用户协议';
} else {
$username = $_POST['username'];
// 必须使用加密后的密码
if (!isset($_POST['password_hash']) || empty($_POST['password_hash'])) {
$error = '无效的请求';
} else {
$password = $_POST['password_hash'];
$rememberMe = isset($_POST['remember_me']) && $_POST['remember_me'] == 'on';
if (login($username, $password, $rememberMe)) {
// 获取用户IP地址
$userIp = getUserIP();
// 检查用户是否同意获取IP
$userId = $_SESSION['user_id'];
$db = Database::getInstance();
$userResult = $db->query("SELECT getip FROM users WHERE id = :user_id", ['user_id' => $userId]);
$userGetIp = !empty($userResult) ? (int)$userResult[0]['getip'] : 1;
if ($userGetIp == 1 && $userIp) {
// 调用IP地理位置API
$locationData = getIpLocation($userIp);
// 更新用户地理位置信息
if ($locationData && isset($locationData['status']) && $locationData['status'] === 'success') {
$db->execute("UPDATE users SET last_ip = :ip, country = :country, province = :province, city = :city WHERE id = :user_id", [
'ip' => $userIp,
'country' => $locationData['country'] ?? '',
'province' => $locationData['regionName'] ?? '',
'city' => $locationData['city'] ?? '',
'user_id' => $userId
]);
} else {
// API调用失败,只更新IP地址
$db->execute("UPDATE users SET last_ip = :ip, country = '', province = '', city = '' WHERE id = :user_id", [
'ip' => $userIp,
'user_id' => $userId
]);
}
} else {
// 用户不同意获取IP,清空地理位置信息
$db->execute("UPDATE users SET last_ip = '', country = '', province = '', city = '' WHERE id = :user_id", [
'user_id' => $userId
]);
}
header('Location: index.php');
exit;
} else {
$error = '用户名或密码错误';
}
} // 关闭 password_hash 检查的 else
}
}
// 处理URL参数中的成功和错误信息
if (isset($_GET['success'])) {
$success = $_GET['success'];
}
if (isset($_GET['error'])) {
$error = $_GET['error'];
}
// 处理注册请求
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['register'])) {
// 验证是否同意协议
if (requireTermsAgreement() && (!isset($_POST['agree_terms']) || $_POST['agree_terms'] != 'on')) {
$error = '请阅读并同意用户协议';
} else {
$username = $_POST['username'];
$email = $_POST['email'];
$password = $_POST['password'];
if (register($username, $password, $email)) {
login($username, $password);
// 获取用户IP地址
$userIp = getUserIP();
// 检查用户是否同意获取IP
$userId = $_SESSION['user_id'];
$db = Database::getInstance();
$userResult = $db->query("SELECT getip FROM users WHERE id = :user_id", ['user_id' => $userId]);
$userGetIp = !empty($userResult) ? (int)$userResult[0]['getip'] : 1;
if ($userGetIp == 1 && $userIp) {
// 调用IP地理位置API
$locationData = getIpLocation($userIp);
// 更新用户地理位置信息
if ($locationData && isset($locationData['status']) && $locationData['status'] === 'success') {
$db->execute("UPDATE users SET last_ip = :ip, country = :country, province = :province, city = :city WHERE id = :user_id", [
'ip' => $userIp,
'country' => $locationData['country'] ?? '',
'province' => $locationData['regionName'] ?? '',
'city' => $locationData['city'] ?? '',
'user_id' => $userId
]);
} else {
// API调用失败,只更新IP地址
$db->execute("UPDATE users SET last_ip = :ip, country = '', province = '', city = '' WHERE id = :user_id", [
'ip' => $userIp,
'user_id' => $userId
]);
}
} else {
// 用户不同意获取IP,清空地理位置信息
$db->execute("UPDATE users SET last_ip = '', country = '', city = '' WHERE id = :user_id", [
'user_id' => $userId
]);
}
header('Location: index.php');
exit;
} else {
$error = '用户名已存在';
}
}
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Parlz - 实时聊天应用</title>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
</head>
<body>
<?php if (!isLoggedIn()): ?>
<!-- 登录/注册页面 -->
<div class="login-container">
<div class="login-box">
<h1 class="app-title">Parlz</h1>
<div class="login-tabs">
<button class="tab-btn active" data-tab="login">登录</button>
<button class="tab-btn" data-tab="register">注册</button>
</div>
<!-- 登录表单 -->
<div id="login" class="tab-content active">
<?php if (isset($error)) echo '<div class="error">' . $error . '</div>'; ?>
<form id="login-form" method="POST">
<div class="input-group" id="login-username-group">
<i class="fas fa-user"></i>
<input type="text" name="username" id="login-username" placeholder="用户名">
<span class="required-mark">*</span>
<div class="error-message" id="login-username-error">请输入用户名</div>
</div>
<div class="input-group" id="login-password-group">
<i class="fas fa-lock"></i>
<input type="password" id="login-password" placeholder="密码">
<input type="hidden" name="password_hash" id="password-hash-field">
<span class="required-mark">*</span>
<div class="error-message" id="login-password-error">请输入密码</div>
</div>
<div class="remember-me">
<input type="checkbox" name="remember_me" id="remember-me">
<label for="remember-me">记住我</label>
</div>
<?php if (requireTermsAgreement()): ?>
<div class="terms-checkbox" id="login-terms-group">
<input type="checkbox" name="agree_terms" id="login-agree">
<label for="login-agree">我已阅读并同意 <a href="terms.php" target="_blank">《Parlz用户协议守则》</a></label>
<div class="error-message" id="login-terms-error">请阅读并同意用户协议</div>
</div>
<?php endif; ?>
<button type="submit" name="login" class="login-btn">登录</button>
</form>
<!-- 第三方登录 -->
<div class="third-party-login">
<div class="login-divider">
<span>或使用以下方式登录</span>
</div>
<button class="third-party-btn qq-login-btn" id="qq-login-btn">
<i class="fab fa-qq"></i>
<span>使用QQ登录</span>
</button>
<button class="third-party-btn github-login-btn" id="github-login-btn">
<i class="fab fa-github"></i>
<span>使用GitHub登录</span>
</button>
<button class="third-party-btn microsoft-login-btn" id="microsoft-login-btn">
<i class="fab fa-microsoft"></i>
<span>使用微软登录</span>
</button>
<button class="third-party-btn steam-login-btn" id="steam-login-btn">
<i class="fab fa-steam"></i>
<span>使用Steam登录</span>
</button>
<button class="third-party-btn discord-login-btn" id="discord-login-btn">
<i class="fab fa-discord"></i>
<span>使用Discord登录</span>
</button>
</div>
</div>
<!-- 注册表单 -->
<div id="register" class="tab-content">
<?php if (isset($error)) echo '<div class="error">' . $error . '</div>'; ?>
<form id="register-form" method="POST">
<div class="input-group" id="register-username-group">
<i class="fas fa-user"></i>
<input type="text" name="username" id="register-username" placeholder="用户名">
<span class="required-mark">*</span>
<div class="error-message" id="register-username-error">请输入用户名</div>
</div>
<div class="input-group" id="register-email-group">
<i class="fas fa-envelope"></i>
<input type="email" name="email" id="register-email" placeholder="邮箱">
<span class="required-mark">*</span>
<div class="error-message" id="register-email-error">请输入邮箱</div>
</div>
<div class="input-group" id="register-password-group">
<i class="fas fa-lock"></i>
<input type="password" name="password" id="register-password" placeholder="密码">
<span class="required-mark">*</span>
<div class="error-message" id="register-password-error">请输入密码(至少6位)</div>
</div>
<?php if (requireTermsAgreement()): ?>
<div class="terms-checkbox" id="register-terms-group">
<input type="checkbox" name="agree_terms" id="register-agree">
<label for="register-agree">我已阅读并同意 <a href="terms.php" target="_blank">《Parlz用户协议守则》</a></label>
<div class="error-message" id="register-terms-error">请阅读并同意用户协议</div>
</div>
<?php endif; ?>
<button type="submit" name="register" class="login-btn">注册</button>
</form>
</div>
<!-- 自定义弹窗提醒 -->
<div id="custom-alert-overlay" class="custom-alert-overlay">
<div class="custom-alert">
<div class="custom-alert-icon">
<i class="fas fa-exclamation-circle"></i>
</div>
<div class="custom-alert-title">提示</div>
<div class="custom-alert-message" id="custom-alert-message"></div>
<button class="custom-alert-btn" id="custom-alert-btn">确定</button>
</div>
</div>
</div>
</div>
<?php else: ?>
<!-- 检查用户是否已绑定邮箱 -->
<?php
$currentUserId = $_SESSION['user_id'];
$userEmail = getUserEmail($currentUserId);
$emailVerified = isEmailVerified($currentUserId);
?>
<!-- 邮箱绑定提醒横幅 -->
<?php if (empty($userEmail) || !$emailVerified): ?>
<div class="email-banner">
<div class="banner-content">
<i class="fas fa-envelope"></i>
<div class="banner-text">
<h4>请绑定并验证您的邮箱</h4>
<p>绑定邮箱后才能添加好友、创建或加入群组</p>
</div>
<button class="banner-btn" id="bind-email-btn">绑定邮箱</button>
</div>
</div>
<?php endif; ?>
<!-- 主聊天界面 -->
<div class="chat-app-container">
<!-- 侧边栏背景遮罩 -->
<div class="sidebar-backdrop" id="sidebar-backdrop"></div>
<div class="chat-app">
<!-- 左侧边栏 -->
<div class="sidebar">
<!-- 顶部用户信息 -->
<div class="sidebar-header">
<div class="user-info">
<div class="avatar" style="cursor: pointer;" onclick="window.location.href='profile.php?name=<?php echo urlencode($_SESSION['username']); ?>'">
<?php
if (!empty($currentUser) && !empty($currentUser['avatar'])) {
echo '<img src="' . htmlspecialchars($currentUser['avatar']) . '" alt="头像" style="width: 100%; height: 100%; border-radius: 50%; object-fit: cover; cursor: pointer;">';
} else {
echo '<i class="fas fa-user-circle" style="cursor: pointer;"></i>';
}
?>
</div>
<div class="username"><?php echo $_SESSION['username']; ?></div>
</div>
<div class="header-actions">
<button class="action-btn" id="add-friend-btn">
<i class="fas fa-user-plus"></i>
</button>
<button class="action-btn" id="friend-requests-btn">
<i class="fas fa-bell"></i>
<span id="request-badge" style="display: none; position: absolute; top: 0; right: 0; background: #ff4d4f; color: white; border-radius: 50%; width: 16px; height: 16px; font-size: 10px; display: flex; align-items: center; justify-content: center;">0</span>
</button>
<button class="action-btn theme-toggle-btn" id="theme-toggle-btn" title="切换主题">
<i class="fas fa-sun sun-icon"></i>
<i class="fas fa-moon moon-icon"></i>
</button>
<div class="dropdown-menu-container">
<button class="action-btn" id="menu-btn">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
</div>
</div>
<!-- 搜索框 -->
<div class="search-box">
<i class="fas fa-search"></i>
<input type="text" placeholder="搜索好友或群组" id="search-friends">
</div>
<!-- 电脑版标签页 -->
<div class="tabs">
<button class="tab-btn active" data-tab="friends">好友</button>
<button class="tab-btn" data-tab="groups">群组</button>
</div>
<!-- 电脑版分开的聊天列表 -->
<div class="chat-lists">
<!-- 好友列表 -->
<div class="tab-content active" id="friends-tab">
<div class="friends-list" id="friends-list">
<!-- 好友列表将通过JavaScript动态生成 -->
</div>
</div>
<!-- 群组列表 -->
<div class="tab-content" id="groups-tab">
<div class="groups-list" id="groups-list">
<!-- 群组列表将通过JavaScript动态生成 -->
</div>
</div>
</div>
<!-- 手机版合并的聊天列表 -->
<div class="mobile-chat-list" id="mobile-chat-list">
<!-- 好友列表区域 -->
<div class="chat-section">
<div class="section-header">
<h3>好友</h3>
</div>
<div class="friends-list-mobile">
<!-- 好友列表将通过JavaScript动态生成 -->
</div>
</div>
<!-- 群组列表区域 -->
<div class="chat-section">
<div class="section-header">
<h3>群组</h3>
</div>
<div class="groups-list-mobile">
<!-- 群组列表将通过JavaScript动态生成 -->
</div>
</div>
</div>
</div>
<!-- 右侧聊天区域 -->
<div class="chat-main">
<!-- 聊天头部 -->
<div class="chat-header" id="chat-header">
<button class="action-btn toggle-sidebar-btn" id="toggle-sidebar-btn">
<i class="fas fa-bars"></i>
</button>
<div class="chat-user-info">
<div class="avatar">
<i class="fas fa-user-circle"></i>
</div>
<div class="username" id="chat-username">选择一个好友开始聊天</div>
</div>
<div class="chat-actions">
<button class="action-btn" id="search-chat-btn">
<i class="fas fa-search"></i>
</button>
</div>
</div>
<!-- 群公告显示区域 -->
<div class="group-announcement" id="group-announcement" style="display: none;">
<div style="display: flex; align-items: flex-start;">
<i class="fas fa-bullhorn"></i>
<div style="flex: 1;">
<div class="announcement-title">群公告</div>
<div id="announcement-content"></div>
<div id="announcement-time" class="announcement-time"></div>
</div>
<button id="close-announcement-btn" class="close-announcement-btn">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<!-- 聊天搜索栏 -->
<div class="chat-search" id="chat-search" style="display: none;">
<div class="search-box">
<i class="fas fa-search"></i>
<input type="text" placeholder="搜索聊天记录" id="search-chat-input">
<button class="action-btn close-search-btn">
<i class="fas fa-times"></i>
</button>
</div>
<div class="search-results" id="search-results" style="display: none;">
<div class="search-header">
<span>搜索结果</span>
<span class="search-count" id="search-count">0</span>
</div>
<div class="search-list" id="search-list">
<!-- 搜索结果将通过JavaScript动态生成 -->
</div>
</div>
</div>
<!-- 聊天内容 -->
<div class="chat-content" id="chat-content">
<div class="empty-chat">
<i class="fas fa-comments"></i>
<p>选择一个好友开始聊天</p>
</div>
</div>
<!-- 聊天输入框 -->
<div class="chat-input-container" id="chat-input-container" style="display: none;">
<div class="input-tools">
<button class="tool-btn">
<i class="fas fa-smile"></i>
</button>
<button class="tool-btn">
<i class="fas fa-image"></i>
</button>
<button class="tool-btn">
<i class="fas fa-paperclip"></i>
</button>
</div>
<div class="input-wrapper">
<textarea id="message-input" placeholder="输入消息..." rows="1"></textarea>
<button class="send-btn" id="send-btn">
<i class="fas fa-paper-plane"></i>
</button>
</div>
</div>
</div>
</div>
<!-- 底部导航栏 -->
<div class="bottom-nav">
<button class="nav-item active" data-nav="chat">
<i class="fas fa-comment"></i>
<span>消息</span>
</button>
<button class="nav-item" data-nav="contacts">
<i class="fas fa-address-book"></i>
<span>通讯录</span>
</button>
<button class="nav-item" data-nav="discover">
<i class="fas fa-compass"></i>
<span>发现</span>
</button>
<button class="nav-item" data-nav="me">
<i class="fas fa-user"></i>
<span>我</span>
</button>
<button class="nav-item theme-toggle-btn" id="theme-toggle-btn-mobile" title="切换主题">
<i class="fas fa-sun sun-icon"></i>
<i class="fas fa-moon moon-icon"></i>
<span>主题</span>
</button>
</div>
</div>
<!-- 添加好友模态框 -->
<div class="modal" id="add-friend-modal">
<div class="modal-content">
<div class="modal-header">
<h3>添加好友</h3>
<button class="close-btn" id="close-modal">&times;</button>
</div>
<div class="modal-body">
<div class="input-group">
<i class="fas fa-user"></i>
<input type="text" id="add-friend-username" placeholder="输入好友用户名">
</div>
<div id="add-friend-result"></div>
</div>
<div class="modal-footer">
<button class="btn btn-cancel" id="cancel-add-friend">取消</button>
<button class="btn btn-primary" id="confirm-add-friend">添加</button>
</div>
</div>
</div>
<!-- 创建群组模态框 -->
<div class="modal" id="create-group-modal">
<div class="modal-content" style="max-width: 500px;">
<div class="modal-header">
<h3>创建群组</h3>
<button class="close-btn" id="close-group-modal">&times;</button>
</div>
<div class="modal-body">
<div class="input-group">
<i class="fas fa-users"></i>
<input type="text" id="group-name" placeholder="输入群组名称">
</div>
<div style="margin-top: 20px;">
<h4 style="margin-bottom: 10px;">选择群成员</h4>
<div id="friends-list-for-group" style="max-height: 200px; overflow-y: auto;">
<!-- 好友列表将通过JavaScript动态生成 -->
</div>
</div>
<div id="create-group-result" style="margin-top: 10px;"></div>
</div>
<div class="modal-footer">
<button class="btn btn-cancel" id="cancel-create-group">取消</button>
<button class="btn btn-primary" id="confirm-create-group">创建</button>
</div>
</div>
</div>
<!-- 通过群号加入群组模态框 -->
<div class="modal" id="join-group-modal">
<div class="modal-content" style="max-width: 400px;">
<div class="modal-header">
<h3>通过群号加入群组</h3>
<button class="close-btn" id="close-join-group-modal">&times;</button>
</div>
<div class="modal-body">
<div class="input-group">
<i class="fas fa-hashtag"></i>
<input type="text" id="join-group-id" placeholder="输入群号">
</div>
<div id="join-group-result" style="margin-top: 10px;"></div>
</div>
<div class="modal-footer">
<button class="btn btn-cancel" id="cancel-join-group">取消</button>
<button class="btn btn-primary" id="confirm-join-group">加入</button>
</div>
</div>
</div>
<!-- 邀请好友加入群组模态框 -->
<div class="modal" id="invite-group-modal">
<div class="modal-content" style="max-width: 500px;">
<div class="modal-header">
<h3>邀请好友加入群组</h3>
<button class="close-btn" id="close-invite-group-modal">&times;</button>
</div>
<div class="modal-body">
<div class="input-group">
<i class="fas fa-user-plus"></i>
<input type="text" id="invite-friend-username" placeholder="输入好友用户名">
</div>
<div id="invite-group-result" style="margin-top: 10px;"></div>
<!-- 好友列表 -->
<div style="margin-top: 20px;">
<h4 style="margin-bottom: 10px;">选择好友</h4>
<div id="friends-list-for-invite" style="max-height: 300px; overflow-y: auto;">
<!-- 好友列表将通过JavaScript动态生成 -->
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-cancel" id="cancel-invite-group">取消</button>
<button class="btn btn-primary" id="confirm-invite-group">邀请</button>
</div>
</div>
</div>
<!-- 群组设置模态框 -->
<div class="modal" id="group-settings-modal">
<div class="modal-content" style="max-width: 500px;">
<div class="modal-header">
<h3>群组设置</h3>
<button class="close-btn" id="close-group-settings-modal">&times;</button>
</div>
<div class="modal-body">
<!-- 修改群头像 -->
<div style="margin-bottom: 20px; text-align: center;">
<h4 style="margin-bottom: 10px;">群头像</h4>
<div style="position: relative; display: inline-block; margin-bottom: 10px;">
<div id="group-avatar-preview" style="width: 100px; height: 100px; border-radius: 50%; overflow: hidden; border: 2px solid #ddd;">
<img src="" alt="群头像" style="width: 100%; height: 100%; object-fit: cover;">
</div>
<button id="change-group-avatar-btn" class="btn btn-primary" style="position: absolute; bottom: 0; right: 0; transform: translate(25%, 25%); border-radius: 50%; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; padding: 0;">
<i class="fas fa-camera"></i>
</button>
<input type="file" id="group-avatar-input" accept="image/*" style="display: none;">
</div>
<div id="group-avatar-result" style="margin-top: 10px;"></div>
</div>
<!-- 修改群组名称 -->
<div style="margin-bottom: 20px;">
<h4 style="margin-bottom: 10px;">群组名称</h4>
<div class="input-group">
<i class="fas fa-users"></i>
<input type="text" id="group-settings-name" placeholder="输入群组名称">
</div>
<div id="group-name-result" style="margin-top: 10px;"></div>
</div>
<!-- 群组公告管理 -->
<div style="margin-bottom: 20px;">
<h4 style="margin-bottom: 10px;">群组公告</h4>
<div id="group-announcement-admin" style="display: none;">
<textarea id="group-announcement-content" placeholder="输入群组公告内容" rows="4" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px;"></textarea>
<button id="save-announcement-btn" class="btn btn-primary" style="margin-top: 10px;">发布公告</button>
<div id="announcement-result" style="margin-top: 10px;"></div>
</div>
<div id="group-announcements-list" style="max-height: 200px; overflow-y: auto;">
<!-- 群组公告列表将通过JavaScript动态生成 -->
</div>
</div>
<!-- 群组成员管理 -->
<div>
<h4 style="margin-bottom: 10px;">群组成员</h4>
<div id="group-members-list" style="max-height: 200px; overflow-y: auto;">
<!-- 群组成员列表将通过JavaScript动态生成 -->
</div>
<div id="group-members-result" style="margin-top: 10px;"></div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-danger" id="delete-group-btn" style="margin-right: auto;">解散群聊</button>
<button class="btn btn-cancel" id="cancel-group-settings">取消</button>
<button class="btn btn-primary" id="save-group-settings">保存设置</button>
</div>
</div>
</div>
<!-- 群组公告模态框 -->
<div class="modal" id="group-announcement-modal">
<div class="modal-content" style="max-width: 500px;">
<div class="modal-header">
<h3>群组公告</h3>
<button class="close-btn" id="close-group-announcement-modal">&times;</button>
</div>
<div class="modal-body">
<div id="group-announcement-admin-view" style="display: none; margin-bottom: 20px;">
<textarea id="group-announcement-content-view" placeholder="输入群组公告内容" rows="4" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px;"></textarea>
<button id="save-announcement-btn-view" class="btn btn-primary" style="margin-top: 10px;">发布公告</button>
<div id="announcement-result-view" style="margin-top: 10px;"></div>
</div>
<div id="group-announcements-list-view" style="max-height: 300px; overflow-y: auto;">
</div>
</div>
<div class="modal-footer">
<button class="btn btn-cancel" id="cancel-group-announcement">关闭</button>
</div>
</div>
</div>
<!-- 好友请求模态框 -->
<div class="modal" id="friend-requests-modal">
<div class="modal-content" style="max-width: 400px;">
<div class="modal-header">
<h3>好友请求</h3>
<button class="close-btn" id="close-friend-requests-modal">&times;</button>
</div>
<div class="modal-body">
<div id="friend-requests-list" style="max-height: 300px; overflow-y: auto;">
<!-- 好友请求列表将通过JavaScript动态生成 -->
</div>
<div id="friend-requests-result" style="margin-top: 10px;"></div>
</div>
<div class="modal-footer">
<button class="btn btn-cancel" id="cancel-friend-requests">关闭</button>
</div>
</div>
</div>
<!-- 绑定邮箱模态框 -->
<div class="modal" id="bind-email-modal">
<div class="modal-content" style="max-width: 400px;">
<div class="modal-header">
<h3>绑定邮箱</h3>
<button class="close-btn" id="close-bind-email-modal">&times;</button>
</div>
<div class="modal-body">
<div class="input-group">
<i class="fas fa-envelope"></i>
<input type="email" id="bind-email-input" placeholder="输入您的邮箱" required>
</div>
<div id="bind-email-result" style="margin-top: 10px;"></div>
</div>
<div class="modal-footer">
<button class="btn btn-cancel" id="cancel-bind-email">取消</button>
<button class="btn btn-primary" id="confirm-bind-email">绑定</button>
</div>
</div>
</div>
<!-- 备注编辑弹窗 -->
<div class="modal" id="set-note-modal">
<div class="modal-content" style="max-width: 400px;">
<div class="modal-header">
<h3>设置备注</h3>
<button class="close-btn" id="close-set-note-modal">&times;</button>
</div>
<div class="modal-body">
<div class="input-group">
<i class="fas fa-sticky-note"></i>
<input type="text" id="note-input" placeholder="输入备注名称" maxlength="50">
</div>
<div style="font-size: 12px; color: #888; margin-top: 8px;">
当前名称: <span id="current-name"></span>
</div>
<input type="hidden" id="note-target-id">
<input type="hidden" id="note-target-type">
</div>
<div class="modal-footer">
<button class="btn btn-cancel" id="cancel-set-note">取消</button>
<button class="btn btn-primary" id="confirm-set-note">保存备注</button>
</div>
</div>
</div>
<!-- 右键菜单 -->
<div class="context-menu" id="context-menu">
<ul>
<li id="context-menu-profile">
<i class="fas fa-user"></i>
<span>名片</span>
</li>
<li id="context-menu-set-note">
<i class="fas fa-sticky-note"></i>
<span>设置备注</span>
</li>
</ul>
<input type="hidden" id="context-menu-target-id">
<input type="hidden" id="context-menu-target-type">
<input type="hidden" id="context-menu-target-name">
</div>
<?php endif; ?>
<!-- 引入JavaScript -->
<script>
// 自定义弹窗函数
function showAlert(message) {
document.getElementById('custom-alert-message').textContent = message;
document.getElementById('custom-alert-overlay').classList.add('show');
}
function hideAlert() {
document.getElementById('custom-alert-overlay').classList.remove('show');
}
// 弹窗关闭按钮事件
document.getElementById('custom-alert-btn').addEventListener('click', hideAlert);
// 点击遮罩层关闭弹窗
document.getElementById('custom-alert-overlay').addEventListener('click', function(e) {
if (e.target === this) {
hideAlert();
}
});
// 显示输入框错误状态
function showFieldError(fieldId, groupId) {
document.getElementById(groupId).classList.add('error');
document.getElementById(fieldId).focus();
}
// 清除输入框错误状态
function clearFieldError(groupId) {
document.getElementById(groupId).classList.remove('error');
}
// SHA-256 哈希函数(纯JavaScript实现)
function sha256(input) {
if (!input || typeof input !== 'string') {
console.error('sha256 函数:输入无效');
return '';
}
const chars = '0123456789abcdef';
const K = [
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
];
function rightRotate(value, amount) {
return (value >>> amount) | (value << (32 - amount));
}
let message = new Uint8Array(input.length);
for (let i = 0; i < input.length; i++) {
message[i] = input.charCodeAt(i);
}
const originalBitLength = message.length * 8;
const paddingLength = (512 - ((originalBitLength + 64) % 512)) % 512;
const paddedMessage = new Uint8Array(message.length + paddingLength / 8 + 8);
paddedMessage.set(message);
paddedMessage[message.length] = 0x80;
for (let i = 0; i < 8; i++) {
paddedMessage[paddedMessage.length - 8 + i] = (originalBitLength >>> (56 - i * 8)) & 0xff;
}
const blocks = [];
for (let i = 0; i < paddedMessage.length; i += 64) {
const block = [];
for (let j = 0; j < 64; j += 4) {
let word = 0;
for (let k = 0; k < 4; k++) {
word = (word << 8) | paddedMessage[i + j + k];
}
block.push(word);
}
blocks.push(block);
}
let h0 = 0x6a09e667, h1 = 0xbb67ae85, h2 = 0x3c6ef372, h3 = 0xa54ff53a;
let h4 = 0x510e527f, h5 = 0x9b05688c, h6 = 0x1f83d9ab, h7 = 0x5be0cd19;
for (const block of blocks) {
const w = new Array(64);
for (let i = 0; i < 16; i++) {
w[i] = block[i];
}
for (let i = 16; i < 64; i++) {
const s0 = rightRotate(w[i - 15], 7) ^ rightRotate(w[i - 15], 18) ^ (w[i - 15] >>> 3);
const s1 = rightRotate(w[i - 2], 17) ^ rightRotate(w[i - 2], 19) ^ (w[i - 2] >>> 10);
w[i] = (w[i - 16] + s0 + w[i - 7] + s1) >>> 0;
}
let a = h0, b = h1, c = h2, d = h3;
let e = h4, f = h5, g = h6, h = h7;
for (let i = 0; i < 64; i++) {
const S1 = rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25);
const ch = (e & f) ^ (~e & g);
const temp1 = (h + S1 + ch + K[i] + w[i]) >>> 0;
const S0 = rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22);
const maj = (a & b) ^ (a & c) ^ (b & c);
const temp2 = (S0 + maj) >>> 0;
h = g;
g = f;
f = e;
e = (d + temp1) >>> 0;
d = c;
c = b;
b = a;
a = (temp1 + temp2) >>> 0;
}
h0 = (h0 + a) >>> 0;
h1 = (h1 + b) >>> 0;
h2 = (h2 + c) >>> 0;
h3 = (h3 + d) >>> 0;
h4 = (h4 + e) >>> 0;
h5 = (h5 + f) >>> 0;
h6 = (h6 + g) >>> 0;
h7 = (h7 + h) >>> 0;
}
const hash = [h0, h1, h2, h3, h4, h5, h6, h7];
let result = '';
for (const word of hash) {
for (let i = 3; i >= 0; i--) {
result += chars[(word >>> (i * 8)) & 0xf];
result += chars[(word >>> (i * 8 + 4)) & 0xf];
}
}
return result;
}
document.getElementById('login-form').addEventListener('submit', function(e) {
const username = document.getElementById('login-username').value.trim();
const password = document.getElementById('login-password').value.trim();
const agreeTerms = document.getElementById('login-agree');
// 清除所有错误状态
clearFieldError('login-username-group');
clearFieldError('login-password-group');
<?php if (requireTermsAgreement()): ?>
clearFieldError('login-terms-group');
<?php endif; ?>
let hasError = false;
if (!username) {
e.preventDefault();
showFieldError('login-username', 'login-username-group');
hasError = true;
}
if (!password) {
e.preventDefault();
showFieldError('login-password', 'login-password-group');
hasError = true;
}
<?php if (requireTermsAgreement()): ?>
if (!agreeTerms.checked) {
e.preventDefault();
showFieldError('login-agree', 'login-terms-group');
hasError = true;
}
<?php endif; ?>
if (hasError) {
e.preventDefault();
showAlert('请填写所有必填项');
return;
}
// 加密密码后再提交
e.preventDefault();
// 计算密码哈希
const hashedPassword = sha256(password);
console.log('计算的密码哈希:', hashedPassword);
console.log('密码哈希长度:', hashedPassword.length);
// 设置隐藏字段值
const passwordHashField = document.getElementById('password-hash-field');
console.log('隐藏字段元素:', passwordHashField);
if (passwordHashField) {
passwordHashField.value = hashedPassword;
console.log('隐藏字段值已设置:', passwordHashField.value);
} else {
console.error('未找到密码哈希隐藏字段!');
}
// 检查表单中的所有字段
const formData = new FormData(this);
console.log('表单数据:');
for (let [key, value] of formData.entries()) {
console.log(key + ': "' + value + '"');
}
// 提交表单
this.submit();
});
// 输入框输入时清除错误状态
document.getElementById('login-username').addEventListener('input', function() {
clearFieldError('login-username-group');
});
document.getElementById('login-password').addEventListener('input', function() {
clearFieldError('login-password-group');
});
<?php if (requireTermsAgreement()): ?>
document.getElementById('login-agree').addEventListener('change', function() {
clearFieldError('login-terms-group');
});
<?php endif; ?>
// 注册表单验证
document.getElementById('register-form').addEventListener('submit', function(e) {
const username = document.getElementById('register-username').value.trim();
const email = document.getElementById('register-email').value.trim();
const password = document.getElementById('register-password').value.trim();
const agreeTerms = document.getElementById('register-agree');
// 清除所有错误状态
clearFieldError('register-username-group');
clearFieldError('register-email-group');
clearFieldError('register-password-group');
<?php if (requireTermsAgreement()): ?>
clearFieldError('register-terms-group');
<?php endif; ?>
let hasError = false;
if (!username) {
showFieldError('register-username', 'register-username-group');
hasError = true;
}
if (!email) {
showFieldError('register-email', 'register-email-group');
hasError = true;
} else {
// 简单的邮箱格式验证
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
document.getElementById('register-email-group').classList.add('error');
document.getElementById('register-email-error').textContent = '请输入有效的邮箱地址';
document.getElementById('register-email').focus();
hasError = true;
}
}
if (!password) {
showFieldError('register-password', 'register-password-group');
hasError = true;
} else if (password.length < 6) {
document.getElementById('register-password-group').classList.add('error');
document.getElementById('register-password-error').textContent = '密码长度不能少于6位';
document.getElementById('register-password').focus();
hasError = true;
}
<?php if (requireTermsAgreement()): ?>
if (!agreeTerms.checked) {
showFieldError('register-agree', 'register-terms-group');
hasError = true;
}
<?php endif; ?>
if (hasError) {
e.preventDefault();
showAlert('请填写所有必填项');
}
});
// 输入框输入时清除错误状态
document.getElementById('register-username').addEventListener('input', function() {
clearFieldError('register-username-group');
});
document.getElementById('register-email').addEventListener('input', function() {
clearFieldError('register-email-group');
document.getElementById('register-email-error').textContent = '请输入邮箱';
});
document.getElementById('register-password').addEventListener('input', function() {
clearFieldError('register-password-group');
document.getElementById('register-password-error').textContent = '请输入密码(至少6位)';
});
<?php if (requireTermsAgreement()): ?>
document.getElementById('register-agree').addEventListener('change', function() {
clearFieldError('register-terms-group');
});
<?php endif; ?>
</script>
<script src="script.js"></script>
<!-- 主菜单 - 移动到body直接子元素中 -->
<div class="dropdown-menu" id="main-menu">
<ul>
<li id="menu-create-group">
<i class="fas fa-users"></i>
<span>创建群组</span>
</li>
<li id="menu-join-group">
<i class="fas fa-sign-in-alt"></i>
<span>加入群组</span>
</li>
<li id="menu-settings">
<i class="fas fa-cog"></i>
<span>设置</span>
</li>
<li id="menu-go-docs">
<i class="fas fa-book"></i>
<span>跳转至文档</span>
</li>
<li class="divider"></li>
<li id="menu-logout">
<i class="fas fa-sign-out-alt"></i>
<span>登出</span>
</li>
</ul>
</div>
</body>
</html>