上传文件至 /
This commit is contained in:
17
pmconfig.php
Normal file
17
pmconfig.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
ini_set('memory_limit', '256M');
|
||||||
|
// pmconfig.php - 数据库配置文件
|
||||||
|
return [
|
||||||
|
'db_host' => 'localhost', // 数据库主机
|
||||||
|
'db_user' => 'a1sax1m9i',
|
||||||
|
'db_pass' => 'a1sax1m9i',
|
||||||
|
'db_name' => 'a1sax1m9i',
|
||||||
|
'oss_access_key' => 'LTAI5tDRQoVXAVA6VrYHRXR9',
|
||||||
|
'oss_secret_key' => 'P8QITod5r465AwBoI11uiwVBCc9SHd',
|
||||||
|
'oss_endpoint' => 'oss-cn-shenzhen.aliyuncs.com',
|
||||||
|
'oss_bucket' => 'sunmusic'
|
||||||
|
];
|
||||||
|
?>
|
||||||
466
profile.php
Normal file
466
profile.php
Normal file
@@ -0,0 +1,466 @@
|
|||||||
|
<?php
|
||||||
|
// 错误处理设置
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
// 初始化变量
|
||||||
|
$message = '';
|
||||||
|
$messageType = '';
|
||||||
|
$userId = 0;
|
||||||
|
$currentNickname = '未设置';
|
||||||
|
$currentEmail = '未设置';
|
||||||
|
$ossConfigured = false;
|
||||||
|
$avatarUrl = './static/icon/icon.png';
|
||||||
|
$ossLoaded = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
// 检查登录状态
|
||||||
|
if (!isset($_SESSION['user_logged_in']) || $_SESSION['user_logged_in'] !== true) {
|
||||||
|
throw new Exception("请先<a href='login.php'>登录</a>");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户ID
|
||||||
|
if (isset($_SESSION['user_id']) && is_numeric($_SESSION['user_id'])) {
|
||||||
|
$userId = (int)$_SESSION['user_id'];
|
||||||
|
} else {
|
||||||
|
throw new Exception("无法获取用户ID,请重新登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载配置文件
|
||||||
|
$config = [];
|
||||||
|
$configFile = 'pmconfig.php';
|
||||||
|
if (file_exists($configFile)) {
|
||||||
|
$config = include $configFile;
|
||||||
|
if (!is_array($config)) {
|
||||||
|
throw new Exception("配置文件必须返回一个数组");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Exception("配置文件 pmconfig.php 不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查OSS配置
|
||||||
|
$ossParams = ['oss_access_key', 'oss_secret_key', 'oss_endpoint', 'oss_bucket'];
|
||||||
|
$missingOssParams = [];
|
||||||
|
foreach ($ossParams as $param) {
|
||||||
|
if (!isset($config[$param]) || empty($config[$param])) {
|
||||||
|
$missingOssParams[] = $param;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$ossConfigured = empty($missingOssParams);
|
||||||
|
|
||||||
|
// 引入OSS SDK
|
||||||
|
if ($ossConfigured) {
|
||||||
|
$ossSdkPath = __DIR__ . '/oss-sdk/autoload.php';
|
||||||
|
if (file_exists($ossSdkPath) && is_readable($ossSdkPath)) {
|
||||||
|
require_once $ossSdkPath;
|
||||||
|
$ossLoaded = class_exists('OSS\OssClient') && class_exists('OSS\Core\OssException');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成头像URL
|
||||||
|
if ($ossConfigured && $ossLoaded) {
|
||||||
|
$bucket = $config['oss_bucket'];
|
||||||
|
$endpoint = preg_replace('/^https?:\/\//', '', $config['oss_endpoint']);
|
||||||
|
$object = 'sunmusic/profile/' . $userId . '头像.png';
|
||||||
|
$avatarUrl = "https://{$bucket}.{$endpoint}/{$object}?t=" . time();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据库连接和用户信息获取
|
||||||
|
$dbRequired = ['db_host', 'db_user', 'db_name'];
|
||||||
|
$dbMissing = [];
|
||||||
|
foreach ($dbRequired as $param) {
|
||||||
|
if (!isset($config[$param]) || empty($config[$param])) {
|
||||||
|
$dbMissing[] = $param;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($dbMissing)) {
|
||||||
|
$conn = new mysqli(
|
||||||
|
$config['db_host'],
|
||||||
|
$config['db_user'],
|
||||||
|
$config['db_pass'] ?? '',
|
||||||
|
$config['db_name']
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
throw new Exception("数据库连接失败: " . $conn->connect_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn->set_charset("utf8mb4");
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
$stmt = $conn->prepare("SELECT nickname, email FROM users WHERE id = ?");
|
||||||
|
$stmt->bind_param("i", $userId);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
if ($userData = $result->fetch_assoc()) {
|
||||||
|
$currentNickname = $userData['nickname'] ?? '未设置';
|
||||||
|
$currentEmail = $userData['email'] ?? '未设置';
|
||||||
|
} else {
|
||||||
|
throw new Exception("未找到用户信息");
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
// 处理基础信息更新
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_basic'])) {
|
||||||
|
$newNickname = trim($_POST['nickname'] ?? '');
|
||||||
|
$newEmail = trim($_POST['email'] ?? '');
|
||||||
|
|
||||||
|
$updates = [];
|
||||||
|
$params = [];
|
||||||
|
$types = '';
|
||||||
|
|
||||||
|
if (!empty($newNickname) && $newNickname !== $currentNickname) {
|
||||||
|
if (strlen($newNickname) < 2 || strlen($newNickname) > 20) {
|
||||||
|
throw new Exception("用户名长度必须在2-20个字符之间");
|
||||||
|
}
|
||||||
|
$updates[] = "nickname = ?";
|
||||||
|
$params[] = $newNickname;
|
||||||
|
$types .= "s";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($newEmail) && $newEmail !== $currentEmail) {
|
||||||
|
if (!filter_var($newEmail, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
throw new Exception("请输入有效的邮箱地址");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查邮箱是否已被使用
|
||||||
|
$checkStmt = $conn->prepare("SELECT id FROM users WHERE email = ? AND id != ?");
|
||||||
|
$checkStmt->bind_param("si", $newEmail, $userId);
|
||||||
|
$checkStmt->execute();
|
||||||
|
$checkResult = $checkStmt->get_result();
|
||||||
|
|
||||||
|
if ($checkResult->num_rows > 0) {
|
||||||
|
throw new Exception("该邮箱已被其他用户使用");
|
||||||
|
}
|
||||||
|
|
||||||
|
$checkStmt->close();
|
||||||
|
|
||||||
|
$updates[] = "email = ?";
|
||||||
|
$params[] = $newEmail;
|
||||||
|
$types .= "s";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($updates)) {
|
||||||
|
$sql = "UPDATE users SET " . implode(", ", $updates) . " WHERE id = ?";
|
||||||
|
$types .= "i";
|
||||||
|
$params[] = $userId;
|
||||||
|
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
$bindParams = array_merge([$types], $params);
|
||||||
|
$tmp = [];
|
||||||
|
foreach ($bindParams as $key => $value) {
|
||||||
|
$tmp[$key] = &$bindParams[$key];
|
||||||
|
}
|
||||||
|
call_user_func_array([$stmt, 'bind_param'], $tmp);
|
||||||
|
$stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
$currentNickname = $newNickname;
|
||||||
|
$currentEmail = $newEmail;
|
||||||
|
$message = "信息更新成功";
|
||||||
|
$messageType = 'success';
|
||||||
|
} else {
|
||||||
|
$message = "没有需要更新的信息";
|
||||||
|
$messageType = 'success';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn->close();
|
||||||
|
} else {
|
||||||
|
throw new Exception("数据库配置不完整(缺少: " . implode(', ', $dbMissing) . ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理头像上传
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['upload_avatar']) && $ossConfigured && $ossLoaded) {
|
||||||
|
if (!isset($_FILES['avatar'])) {
|
||||||
|
throw new Exception("未收到上传文件");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查上传错误
|
||||||
|
switch ($_FILES['avatar']['error']) {
|
||||||
|
case UPLOAD_ERR_OK:
|
||||||
|
break;
|
||||||
|
case UPLOAD_ERR_NO_FILE:
|
||||||
|
throw new Exception("请选择要上传的图片");
|
||||||
|
case UPLOAD_ERR_INI_SIZE:
|
||||||
|
case UPLOAD_ERR_FORM_SIZE:
|
||||||
|
throw new Exception("文件过大,超过上传限制");
|
||||||
|
default:
|
||||||
|
throw new Exception("上传错误,代码: " . $_FILES['avatar']['error']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$avatarFile = $_FILES['avatar'];
|
||||||
|
|
||||||
|
// 验证文件
|
||||||
|
if (!is_uploaded_file($avatarFile['tmp_name'])) {
|
||||||
|
throw new Exception("文件上传异常");
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileInfo = getimagesize($avatarFile['tmp_name']);
|
||||||
|
if (!$fileInfo) {
|
||||||
|
throw new Exception("不是有效的图片文件");
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowedMime = ['image/jpeg', 'image/png', 'image/gif'];
|
||||||
|
if (!in_array($fileInfo['mime'], $allowedMime)) {
|
||||||
|
throw new Exception("不支持的图片类型");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($avatarFile['size'] > 10 * 1024 * 1024) {
|
||||||
|
throw new Exception("文件过大,最大支持10MB");
|
||||||
|
}
|
||||||
|
|
||||||
|
// OSS上传
|
||||||
|
$object = 'sunmusic/profile/' . $userId . '头像.png';
|
||||||
|
try {
|
||||||
|
$ossClient = new OSS\OssClient(
|
||||||
|
$config['oss_access_key'],
|
||||||
|
$config['oss_secret_key'],
|
||||||
|
$config['oss_endpoint']
|
||||||
|
);
|
||||||
|
|
||||||
|
$options = [
|
||||||
|
OSS\OssClient::OSS_HEADERS => [
|
||||||
|
'x-oss-object-acl' => 'public-read',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$ossClient->uploadFile(
|
||||||
|
$config['oss_bucket'],
|
||||||
|
$object,
|
||||||
|
$avatarFile['tmp_name'],
|
||||||
|
$options
|
||||||
|
);
|
||||||
|
|
||||||
|
$endpoint = preg_replace('/^https?:\/\//', '', $config['oss_endpoint']);
|
||||||
|
$avatarUrl = "https://{$config['oss_bucket']}.{$endpoint}/{$object}?t=" . time();
|
||||||
|
$message = "头像上传成功!";
|
||||||
|
$messageType = 'success';
|
||||||
|
} catch (OSS\Core\OssException $e) {
|
||||||
|
throw new Exception("OSS错误: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$message = $e->getMessage();
|
||||||
|
$messageType = 'error';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测设备类型
|
||||||
|
$isMobile = preg_match('/(android|iphone|ipad|ipod|blackberry|windows phone)/i', $_SERVER['HTTP_USER_AGENT']);
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN" class="<?php echo $isMobile ? 'mobile' : 'desktop'; ?>">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>个人信息</title>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
<style>
|
||||||
|
/* === 引入自定义字体 === */
|
||||||
|
@font-face {
|
||||||
|
font-family: "MyCustomFont";
|
||||||
|
src: url("./static/font/bbc.ttf") format("truetype");
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
/* 基础样式 */
|
||||||
|
:root {
|
||||||
|
--bg-color: #f5f5f7;
|
||||||
|
--text-color: #333;
|
||||||
|
--card-bg: #ffffff;
|
||||||
|
--card-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||||
|
--primary-color: #2c3e50;
|
||||||
|
--primary-hover: #34495e;
|
||||||
|
--border-color: #ddd;
|
||||||
|
--accent-color: #e74c3c;
|
||||||
|
--main-font: "MyCustomFont", sans-serif; /* 自定义字体变量 */
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--main-font);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 布局样式 */
|
||||||
|
.app-container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-container {
|
||||||
|
background-color: var(--card-bg);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 2rem;
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标题样式 */
|
||||||
|
h1, h2 {
|
||||||
|
color: var(--primary-color);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 头像样式 */
|
||||||
|
.avatar-container {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
padding-bottom: 1.5rem;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 180px;
|
||||||
|
height: 180px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
border: 3px solid var(--primary-color);
|
||||||
|
margin: 0 auto 1rem;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-upload {
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border: 1px dashed var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: rgba(44, 62, 80, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表单样式 */
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: var(--primary-color);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.8rem;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 1rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮样式 */
|
||||||
|
.submit-btn {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 0.8rem 2rem;
|
||||||
|
border-radius: 25px;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn:hover {
|
||||||
|
background-color: var(--primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--primary-color);
|
||||||
|
border: 1px solid var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 消息提示样式 */
|
||||||
|
.message {
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
background-color: #d4edda;
|
||||||
|
color: #155724;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
background-color: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="app-container">
|
||||||
|
<h1>个人信息管理</h1>
|
||||||
|
|
||||||
|
<div class="profile-container">
|
||||||
|
<?php if (!empty($message)): ?>
|
||||||
|
<div class="message <?php echo $messageType; ?>"><?php echo $message; ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- 头像上传区域 -->
|
||||||
|
<div class="avatar-container">
|
||||||
|
<h2>个人头像</h2>
|
||||||
|
<img src="<?php echo htmlspecialchars($avatarUrl); ?>" class="avatar"
|
||||||
|
alt="用户头像" onerror="this.src='./static/icon/icon.png'">
|
||||||
|
|
||||||
|
<div class="avatar-upload">
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
<input type="file" name="avatar" accept="image/*" class="form-control">
|
||||||
|
<p></p>
|
||||||
|
<button type="submit" name="upload_avatar" class="submit-btn"
|
||||||
|
<?php echo !$ossConfigured ? 'disabled title="OSS配置不完整,无法上传"' : ''; ?>>
|
||||||
|
上传头像
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 个人信息编辑区域 -->
|
||||||
|
<form method="post">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="nickname">用户名</label>
|
||||||
|
<input type="text" id="nickname" name="nickname"
|
||||||
|
value="<?php echo htmlspecialchars($currentNickname); ?>"
|
||||||
|
class="form-control" maxlength="20">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email">电子邮箱</label>
|
||||||
|
<input type="email" id="email" name="email"
|
||||||
|
value="<?php echo htmlspecialchars($currentEmail); ?>"
|
||||||
|
class="form-control">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<a href="index.php" class="submit-btn btn-secondary">返回</a>
|
||||||
|
<button type="submit" name="update_basic" class="submit-btn">保存修改</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
284
register.php
Normal file
284
register.php
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
<?php
|
||||||
|
// 开启错误显示以便调试
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
// 启动会话
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
// 修复跳转逻辑:仅当用户已登录时才跳转到首页
|
||||||
|
// 已登录用户不需要注册,直接跳转
|
||||||
|
if (isset($_SESSION['user_logged_in']) && $_SESSION['user_logged_in'] === true) {
|
||||||
|
header('Location: index.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据库连接配置 - 与登录页面保持一致
|
||||||
|
$servername = "localhost";
|
||||||
|
$dbusername = "a1sax1m9i"; // 与登录页面相同的数据库用户名
|
||||||
|
$dbpassword = "a1sax1m9i"; // 与登录页面相同的数据库密码
|
||||||
|
$dbname = "a1sax1m9i"; // 与登录页面相同的数据库名
|
||||||
|
|
||||||
|
// 初始化消息变量
|
||||||
|
$error = "";
|
||||||
|
$success = false;
|
||||||
|
|
||||||
|
// 处理表单提交
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||||
|
// 获取表单数据并过滤
|
||||||
|
$username = trim($_POST['username'] ?? '');
|
||||||
|
$email = trim($_POST['email'] ?? '');
|
||||||
|
$password = $_POST['password'] ?? '';
|
||||||
|
$confirm_password = $_POST['confirm_password'] ?? '';
|
||||||
|
$nickname = trim($_POST['nickname'] ?? $username); // 昵称默认为用户名
|
||||||
|
|
||||||
|
// 表单验证
|
||||||
|
if (empty($username)) {
|
||||||
|
$error = "用户名不能为空";
|
||||||
|
} elseif (strlen($username) < 3 || strlen($username) > 20) {
|
||||||
|
$error = "用户名长度必须在3-20个字符之间";
|
||||||
|
} elseif (empty($email)) {
|
||||||
|
$error = "邮箱不能为空";
|
||||||
|
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
$error = "请输入有效的邮箱地址";
|
||||||
|
} elseif (empty($password)) {
|
||||||
|
$error = "密码不能为空";
|
||||||
|
} elseif (strlen($password) < 6) {
|
||||||
|
$error = "密码长度不能少于6个字符";
|
||||||
|
} elseif ($password !== $confirm_password) {
|
||||||
|
$error = "两次输入的密码不一致";
|
||||||
|
} else {
|
||||||
|
// 连接数据库
|
||||||
|
$conn = new mysqli($servername, $dbusername, $dbpassword, $dbname);
|
||||||
|
|
||||||
|
// 检查数据库连接
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
$error = "数据库连接失败: " . $conn->connect_error .
|
||||||
|
"<br>使用的连接信息:<br>" .
|
||||||
|
"服务器: " . htmlspecialchars($servername) . "<br>" .
|
||||||
|
"用户名: " . htmlspecialchars($dbusername) . "<br>" .
|
||||||
|
"数据库名: " . htmlspecialchars($dbname);
|
||||||
|
} else {
|
||||||
|
// 检查用户名或邮箱是否已存在
|
||||||
|
$sql = "SELECT id FROM users WHERE username = ? OR email = ?";
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
$error = "准备查询语句失败: " . $conn->error;
|
||||||
|
} else {
|
||||||
|
$stmt->bind_param("ss", $username, $email);
|
||||||
|
$stmt->execute();
|
||||||
|
$stmt->store_result();
|
||||||
|
|
||||||
|
if ($stmt->num_rows > 0) {
|
||||||
|
$error = "用户名或邮箱已被注册";
|
||||||
|
$stmt->close();
|
||||||
|
} else {
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
// 密码加密
|
||||||
|
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
|
||||||
|
// 插入新用户
|
||||||
|
$sql = "INSERT INTO users (username, email, password_hash, nickname, created_at)
|
||||||
|
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)";
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
$error = "准备插入语句失败: " . $conn->error;
|
||||||
|
} else {
|
||||||
|
$stmt->bind_param("ssss", $username, $email, $hashed_password, $nickname);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
$success = true;
|
||||||
|
|
||||||
|
// 注册成功后自动登录(可选)
|
||||||
|
$_SESSION['user_logged_in'] = true;
|
||||||
|
$_SESSION['user_id'] = $conn->insert_id;
|
||||||
|
$_SESSION['user_info'] = [
|
||||||
|
'username' => $username,
|
||||||
|
'email' => $email,
|
||||||
|
'nickname' => $nickname
|
||||||
|
];
|
||||||
|
|
||||||
|
// 延迟跳转,让用户看到成功信息
|
||||||
|
if ($_POST['auto_login'] ?? true) {
|
||||||
|
header('Refresh: 2; URL=index.php');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$error = "注册失败: " . $stmt->error;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>用户注册 - 音乐分享网站</title>
|
||||||
|
<link rel="icon" href="./static/icon/icon.png" type="image/png">
|
||||||
|
<link rel="alternate icon" href="./static/icon/icon.ico" type="image/x-icon">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: "MyCustomFont";
|
||||||
|
src: url("./static/font/bbc.ttf") format("truetype");
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
:root{
|
||||||
|
--main-font: "MyCustomFont", sans-serif; /* 自定义字体变量 */
|
||||||
|
}
|
||||||
|
/* 基础样式 */
|
||||||
|
body {
|
||||||
|
font-family: var(--main-font); /* 应用自定义字体 */
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 0 1rem;
|
||||||
|
background-color: #f5f5f7;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
background-color: white;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.8rem;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.8rem;
|
||||||
|
background-color: #2c3e50;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background-color: #34495e;
|
||||||
|
}
|
||||||
|
.error-message {
|
||||||
|
background-color: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.success-message {
|
||||||
|
background-color: #d4edda;
|
||||||
|
color: #155724;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.login-link {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
.login-link a {
|
||||||
|
color: #2c3e50;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.login-link a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.form-hint {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 0.3rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1><i class="fas fa-user-plus"></i> 用户注册</h1>
|
||||||
|
|
||||||
|
<?php if (!empty($error)): ?>
|
||||||
|
<div class="error-message">
|
||||||
|
<i class="fas fa-exclamation-circle"></i> <?php echo nl2br(htmlspecialchars($error)); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($success): ?>
|
||||||
|
<div class="success-message">
|
||||||
|
<i class="fas fa-check-circle"></i> 注册成功!感谢您的加入<br>
|
||||||
|
2秒后将自动跳转到首页...
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username">用户名</label>
|
||||||
|
<input type="text" id="username" name="username"
|
||||||
|
value="<?php echo htmlspecialchars($_POST['username'] ?? ''); ?>" required>
|
||||||
|
<div class="form-hint">3-20个字符,可包含字母、数字和下划线</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="nickname">昵称(可选)</label>
|
||||||
|
<input type="text" id="nickname" name="nickname"
|
||||||
|
value="<?php echo htmlspecialchars($_POST['nickname'] ?? ''); ?>">
|
||||||
|
<div class="form-hint">显示给其他用户的名称,不填则默认与用户名相同</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email">邮箱</label>
|
||||||
|
<input type="email" id="email" name="email"
|
||||||
|
value="<?php echo htmlspecialchars($_POST['email'] ?? ''); ?>" required>
|
||||||
|
<div class="form-hint">用于登录和密码找回</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">密码</label>
|
||||||
|
<input type="password" id="password" name="password" required>
|
||||||
|
<div class="form-hint">至少6个字符,建议包含字母和数字</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="confirm_password">确认密码</label>
|
||||||
|
<input type="password" id="confirm_password" name="confirm_password" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="hidden" name="auto_login" value="1">
|
||||||
|
<button type="submit"><i class="fas fa-register"></i> 注册账号</button>
|
||||||
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="login-link">
|
||||||
|
已有账号?<a href="login.php">立即登录</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
273
reset_password.php
Normal file
273
reset_password.php
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
<?php
|
||||||
|
// 开启错误显示以便调试
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
// 启动会话
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
// 检查用户是否已登录,如果已登录则重定向到首页
|
||||||
|
if (isset($_SESSION['user_logged_in']) && $_SESSION['user_logged_in'] === true) {
|
||||||
|
header('Location: index.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据库连接配置 - 与其他页面保持一致
|
||||||
|
$servername = "localhost";
|
||||||
|
$username = "a1sax1m9i"; // 与其他页面相同
|
||||||
|
$password = "a1sax1m9i"; // 与其他页面相同
|
||||||
|
$dbname = "a1sax1m9i"; // 与其他页面相同
|
||||||
|
|
||||||
|
// 初始化消息变量
|
||||||
|
$message = "";
|
||||||
|
$messageType = ""; // success 或 error
|
||||||
|
$tokenValid = false;
|
||||||
|
$userId = null;
|
||||||
|
|
||||||
|
// 确保密码重置表存在
|
||||||
|
function ensurePasswordResetTableExists($conn) {
|
||||||
|
$sql = "CREATE TABLE IF NOT EXISTS password_reset_tokens (
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
token VARCHAR(255) NOT NULL,
|
||||||
|
expiry DATETIME NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (user_id),
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
|
||||||
|
|
||||||
|
if (!$conn->query($sql)) {
|
||||||
|
return "创建密码重置表失败: " . $conn->error;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查令牌是否存在
|
||||||
|
if (!isset($_GET['token']) || empty($_GET['token'])) {
|
||||||
|
$message = "无效的重置链接";
|
||||||
|
$messageType = "error";
|
||||||
|
} else {
|
||||||
|
$token = $_GET['token'];
|
||||||
|
|
||||||
|
// 连接数据库
|
||||||
|
$conn = new mysqli($servername, $username, $password, $dbname);
|
||||||
|
|
||||||
|
// 检查数据库连接
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
$message = "数据库连接失败: " . $conn->connect_error;
|
||||||
|
$messageType = "error";
|
||||||
|
} else {
|
||||||
|
// 确保密码重置表存在
|
||||||
|
$tableCheck = ensurePasswordResetTableExists($conn);
|
||||||
|
if ($tableCheck !== true) {
|
||||||
|
$message = $tableCheck;
|
||||||
|
$messageType = "error";
|
||||||
|
} else {
|
||||||
|
// 检查令牌是否有效且未过期
|
||||||
|
$sql = "SELECT user_id FROM password_reset_tokens WHERE token = ? AND expiry > NOW()";
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
$message = "准备查询语句失败: " . $conn->error;
|
||||||
|
$messageType = "error";
|
||||||
|
} else {
|
||||||
|
$stmt->bind_param("s", $token);
|
||||||
|
$stmt->execute();
|
||||||
|
$stmt->store_result();
|
||||||
|
|
||||||
|
if ($stmt->num_rows == 1) {
|
||||||
|
// 令牌有效
|
||||||
|
$stmt->bind_result($userId);
|
||||||
|
$stmt->fetch();
|
||||||
|
$tokenValid = true;
|
||||||
|
} else {
|
||||||
|
$message = "重置链接无效或已过期";
|
||||||
|
$messageType = "error";
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理表单提交
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] == "POST" && $tokenValid && $userId !== null) {
|
||||||
|
// 获取表单数据并过滤
|
||||||
|
$newPassword = $_POST['new_password'] ?? '';
|
||||||
|
$confirmPassword = $_POST['confirm_password'] ?? '';
|
||||||
|
|
||||||
|
// 表单验证
|
||||||
|
if (empty($newPassword)) {
|
||||||
|
$message = "新密码不能为空";
|
||||||
|
$messageType = "error";
|
||||||
|
} elseif (strlen($newPassword) < 6) {
|
||||||
|
$message = "密码长度不能少于6个字符";
|
||||||
|
$messageType = "error";
|
||||||
|
} elseif ($newPassword !== $confirmPassword) {
|
||||||
|
$message = "两次输入的密码不一致";
|
||||||
|
$messageType = "error";
|
||||||
|
} else {
|
||||||
|
// 连接数据库
|
||||||
|
$conn = new mysqli($servername, $username, $password, $dbname);
|
||||||
|
|
||||||
|
// 检查数据库连接
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
$message = "数据库连接失败: " . $conn->connect_error;
|
||||||
|
$messageType = "error";
|
||||||
|
} else {
|
||||||
|
// 哈希密码
|
||||||
|
$hashedPassword = password_hash($newPassword, PASSWORD_DEFAULT);
|
||||||
|
|
||||||
|
// 更新用户密码
|
||||||
|
$sql = "UPDATE users SET password_hash = ? WHERE id = ?";
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
$message = "准备更新语句失败: " . $conn->error;
|
||||||
|
$messageType = "error";
|
||||||
|
} else {
|
||||||
|
$stmt->bind_param("si", $hashedPassword, $userId);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
// 密码更新成功,删除令牌
|
||||||
|
$deleteStmt = $conn->prepare("DELETE FROM password_reset_tokens WHERE user_id = ?");
|
||||||
|
$deleteStmt->bind_param("i", $userId);
|
||||||
|
$deleteStmt->execute();
|
||||||
|
$deleteStmt->close();
|
||||||
|
|
||||||
|
$message = "密码已成功重置,请使用新密码登录";
|
||||||
|
$messageType = "success";
|
||||||
|
$tokenValid = false; // 防止再次提交
|
||||||
|
} else {
|
||||||
|
$message = "密码更新失败,请稍后再试";
|
||||||
|
$messageType = "error";
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>重置密码 - 音乐分享网站</title>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
<style>
|
||||||
|
/* 基础样式 */
|
||||||
|
body {
|
||||||
|
font-family: 'Arial', sans-serif;
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 0 1rem;
|
||||||
|
background-color: #f5f5f7;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
background-color: white;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.8rem;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.8rem;
|
||||||
|
background-color: #2c3e50;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background-color: #34495e;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.error-message {
|
||||||
|
background-color: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
.success-message {
|
||||||
|
background-color: #d4edda;
|
||||||
|
color: #155724;
|
||||||
|
}
|
||||||
|
.login-link {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
.login-link a {
|
||||||
|
color: #2c3e50;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.login-link a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1><i class="fas fa-redo"></i> 重置密码</h1>
|
||||||
|
|
||||||
|
<?php if (!empty($message)): ?>
|
||||||
|
<div class="message <?php echo $messageType === 'error' ? 'error-message' : 'success-message'; ?>">
|
||||||
|
<i class="fas <?php echo $messageType === 'error' ? 'fa-exclamation-circle' : 'fa-check-circle'; ?>"></i>
|
||||||
|
<?php echo nl2br(htmlspecialchars($message)); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($tokenValid): ?>
|
||||||
|
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]) . '?token=' . htmlspecialchars($_GET['token']); ?>">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="new_password">新密码</label>
|
||||||
|
<input type="password" id="new_password" name="new_password" minlength="6" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="confirm_password">确认新密码</label>
|
||||||
|
<input type="password" id="confirm_password" name="confirm_password" minlength="6" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit">重置密码</button>
|
||||||
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="login-link">
|
||||||
|
<a href="login.php"><i class="fas fa-sign-in-alt"></i> 返回登录</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
667
sou.php
Normal file
667
sou.php
Normal file
@@ -0,0 +1,667 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'config.php';
|
||||||
|
|
||||||
|
// 页面标题
|
||||||
|
$pageTitle = "音乐搜索 - 落日音乐";
|
||||||
|
|
||||||
|
// 设备识别函数
|
||||||
|
function isMobileDevice() {
|
||||||
|
$userAgent = $_SERVER['HTTP_USER_AGENT'];
|
||||||
|
$mobileKeywords = [
|
||||||
|
'Android', 'webOS', 'iPhone', 'iPad', 'iPod', 'BlackBerry',
|
||||||
|
'Windows Phone', 'Opera Mini', 'IEMobile', 'Mobile'
|
||||||
|
];
|
||||||
|
foreach ($mobileKeywords as $keyword) {
|
||||||
|
if (stripos($userAgent, $keyword) !== false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$isMobile = isMobileDevice();
|
||||||
|
$htmlClass = $isMobile ? 'mobile' : 'desktop';
|
||||||
|
|
||||||
|
// 检查用户登录状态
|
||||||
|
session_start();
|
||||||
|
$isLoggedIn = isset($_SESSION['user_logged_in']) && $_SESSION['user_logged_in'] === true;
|
||||||
|
$userInfo = isset($_SESSION['user_info']) ? $_SESSION['user_info'] : null;
|
||||||
|
|
||||||
|
// 处理登出
|
||||||
|
if (isset($_GET['action']) && $_GET['action'] == 'logout') {
|
||||||
|
session_unset();
|
||||||
|
session_destroy();
|
||||||
|
header("Location: index.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义音乐分类及其配色
|
||||||
|
$categories = [
|
||||||
|
'all' => ['name' => '全部音乐', 'color' => '#b89e81', 'text_color' => '#5d4037'],
|
||||||
|
'cantonese' => ['name' => '粤语歌曲', 'color' => '#c8e6c9', 'text_color' => '#2e7d32'],
|
||||||
|
'mandarin' => ['name' => '国语歌曲', 'color' => '#fff3e0', 'text_color' => '#e65100'],
|
||||||
|
'waiyu' => ['name' => '外语歌曲', 'color' => '#e3f2fd', 'text_color' => '#0d47a1'],
|
||||||
|
'classic' => ['name' => '经典老歌', 'color' => '#efebe9', 'text_color' => '#3e2723'],
|
||||||
|
'other' => ['name' => '其他音乐', 'color' => '#f3e5f5', 'text_color' => '#6a1b9a']
|
||||||
|
];
|
||||||
|
|
||||||
|
// 获取音乐列表数据
|
||||||
|
$musicList = require_once __DIR__ . '/data/music.php';
|
||||||
|
|
||||||
|
// 获取并清理搜索词
|
||||||
|
$searchTerm = isset($_GET['s']) ? trim($_GET['s']) : '';
|
||||||
|
|
||||||
|
// 搜索功能:匹配歌名或歌手(不区分大小写)
|
||||||
|
$searchResults = [];
|
||||||
|
if (!empty($searchTerm)) {
|
||||||
|
$lowerTerm = strtolower($searchTerm);
|
||||||
|
foreach ($musicList as $music) {
|
||||||
|
if (strpos(strtolower($music['title']), $lowerTerm) !== false ||
|
||||||
|
strpos(strtolower($music['artist']), $lowerTerm) !== false) {
|
||||||
|
$searchResults[] = $music;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前页面URL
|
||||||
|
function getCurrentPageURL() {
|
||||||
|
$protocol = ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
|
||||||
|
$host = $_SERVER['HTTP_HOST'];
|
||||||
|
$script = $_SERVER['SCRIPT_NAME'];
|
||||||
|
return $protocol . $host . $script;
|
||||||
|
}
|
||||||
|
$currentPageUrl = getCurrentPageURL();
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh_cn" class="<?php echo $htmlClass; ?>">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
|
<title><?php echo htmlspecialchars($pageTitle); ?></title>
|
||||||
|
<meta name="copyright" content="本网站内容受版权保护,未经许可不得复制或使用">
|
||||||
|
<link rel="icon" href="./static/icon/icon.png" type="image/png">
|
||||||
|
<link rel="alternate icon" href="./static/icon/icon.ico" type="image/x-icon">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: "MyCustomFont";
|
||||||
|
src: url("./static/font/bbc.ttf") format("truetype");
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
/* === 基础样式与暗黑模式变量 === */
|
||||||
|
:root {
|
||||||
|
--bg-color: #f5f5f7;
|
||||||
|
--text-color: #333;
|
||||||
|
--card-bg: #ffffff;
|
||||||
|
--card-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||||
|
--primary-color: #2c3e50;
|
||||||
|
--primary-hover: #34495e;
|
||||||
|
--muted-color: #666;
|
||||||
|
--border-color: #ddd;
|
||||||
|
--accent-color: #e74c3c;
|
||||||
|
--notification-bg: #2c3e50;
|
||||||
|
--notification-text: #ffffff;
|
||||||
|
--main-font: "MyCustomFont", sans-serif; /* 自定义字体变量 */
|
||||||
|
}
|
||||||
|
.dark-mode {
|
||||||
|
--bg-color: #121212;
|
||||||
|
--text-color: #e0e0e0;
|
||||||
|
--card-bg: #1e1e1e;
|
||||||
|
--card-shadow: 0 4px 12px rgba(0,0,0,0.5);
|
||||||
|
--primary-color: #bb86fc;
|
||||||
|
--primary-hover: #9c6cd6;
|
||||||
|
--muted-color: #b0b0b0;
|
||||||
|
--border-color: #333;
|
||||||
|
--accent-color: #ff6b6b;
|
||||||
|
--notification-bg: #111;
|
||||||
|
--notification-text: #e0e0e0;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: var(--main-font), /* 应用自定义字体 */ sans-serif; margin: 0; padding: 0; background-color: var(--bg-color); color: var(--text-color); transition: background-color 0.3s ease, color 0.3s ease; }
|
||||||
|
.app-container { display: flex; flex-direction: column; min-height: 100vh; }
|
||||||
|
.top-bar { display: flex; justify-content: space-between; align-items: center; padding: 1rem 1.5rem; background-color: var(--card-bg); box-shadow: 0 2px 8px rgba(0,0,0,0.1); position: sticky; top: 0; z-index: 100; transition: background-color 0.3s ease; }
|
||||||
|
.site-title { margin: 0; font-size: 1.4rem; color: var(--primary-color); }
|
||||||
|
.theme-toggle, .user-menu-btn, .back-link { background: none; border: 1px solid var(--border-color); color: var(--text-color); padding: 0.5rem 0.8rem; border-radius: 20px; cursor: pointer; display: flex; align-items: center; gap: 0.5rem; transition: all 0.3s ease; text-decoration: none; }
|
||||||
|
.theme-toggle:hover, .user-menu-btn:hover, .back-link:hover { background-color: var(--primary-color); color: white; border-color: var(--primary-color); }
|
||||||
|
.main-content { flex: 1; max-width: 1200px; width: 100%; margin: 0 auto; padding: 1rem; box-sizing: border-box; }
|
||||||
|
h1, h2, h3 { color: var(--primary-color); }
|
||||||
|
h1 { font-size: 1.8rem; text-align: center; margin: 1rem 0; }
|
||||||
|
|
||||||
|
/* 搜索框 */
|
||||||
|
.search-container { margin: 1rem 0; text-align: center; }
|
||||||
|
#search-form { max-width: 600px; margin: 0 auto; }
|
||||||
|
#search-input { width: 100%; max-width: 600px; padding: 0.8rem 1rem; border: 1px solid var(--border-color); border-radius: 25px; font-size: 1rem; background-color: var(--card-bg); color: var(--text-color); transition: all 0.3s ease; }
|
||||||
|
#search-input:focus { outline: none; border-color: var(--primary-color); box-shadow: 0 0 0 2px rgba(44, 62, 80, 0.1); }
|
||||||
|
|
||||||
|
/* 音乐项样式 */
|
||||||
|
.music-item { background-color: var(--card-bg); border-radius: 12px; padding: 1.2rem; box-shadow: var(--card-shadow); margin-bottom: 1.2rem; transition: background-color 0.3s ease; }
|
||||||
|
.category-tag { display: inline-block; padding: 0.3rem 0.8rem; border-radius: 15px; font-size: 0.8rem; font-weight: 500; margin-right: 0.5rem; margin-bottom: 0.8rem; }
|
||||||
|
.artist-info { color: var(--muted-color); font-size: 0.9rem; margin-bottom: 1rem; font-style: italic; }
|
||||||
|
|
||||||
|
/* 播放器、视频、下载、分享 样式 */
|
||||||
|
.custom-audio-player, .video-container, .download-section, .share-section { margin-top: 1rem; }
|
||||||
|
.custom-audio-player { width: 100%; background-color: var(--bg-color); border-radius: 8px; padding: 0.8rem; display: flex; align-items: center; gap: 0.8rem; flex-wrap: wrap; }
|
||||||
|
.audio-controls, .volume-control { display: flex; align-items: center; gap: 0.8rem; }
|
||||||
|
.audio-button, .volume-button { background: none; border: none; cursor: pointer; color: var(--primary-color); font-size: 1.2rem; width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; }
|
||||||
|
.audio-button:hover, .volume-button:hover { background-color: rgba(44, 62, 80, 0.1); }
|
||||||
|
.audio-button.active { background-color: var(--primary-color); color: white; }
|
||||||
|
.progress-container { flex-grow: 1; display: flex; align-items: center; gap: 0.5rem; min-width: 200px; }
|
||||||
|
.audio-progress { flex-grow: 1; height: 4px; background-color: var(--border-color); border-radius: 2px; cursor: pointer; position: relative; }
|
||||||
|
.progress-fill { height: 100%; background-color: var(--primary-color); border-radius: 2px; width: 0%; }
|
||||||
|
.progress-handle { position: absolute; width: 12px; height: 12px; border-radius: 50%; background-color: var(--primary-color); top: 50%; transform: translateY(-50%); left: 0%; display: none; box-shadow: 0 0 5px rgba(0,0,0,0.2); }
|
||||||
|
.audio-progress:hover .progress-handle { display: block; }
|
||||||
|
.time-display, .time-separator { font-size: 0.8rem; color: var(--muted-color); width: 40px; text-align: center; }
|
||||||
|
.volume-slider { width: 80px; height: 4px; -webkit-appearance: none; appearance: none; background: var(--border-color); border-radius: 2px; outline: none; }
|
||||||
|
.volume-slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 12px; height: 12px; border-radius: 50%; background: var(--primary-color); cursor: pointer; }
|
||||||
|
|
||||||
|
.video-container { position: relative; width: 100%; padding-top: 56.25%; border-radius: 8px; overflow: hidden; }
|
||||||
|
.video-container iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none; }
|
||||||
|
.download-link { display: inline-block; color: var(--primary-color); text-decoration: none; padding: 0.5rem 1rem; border: 1px solid var(--primary-color); border-radius: 20px; transition: all 0.3s ease; font-size: 0.95rem; }
|
||||||
|
.download-link:hover { background-color: var(--primary-color); color: white; }
|
||||||
|
.share-section { display: flex; align-items: center; gap: 0.5rem; }
|
||||||
|
.share-link-container { display: flex; align-items: center; gap: 0.5rem; flex-grow: 1; }
|
||||||
|
.share-link { flex-grow: 1; padding: 0.5rem; border: 1px solid var(--border-color); border-radius: 4px; font-size: 0.9rem; color: var(--text-color); background-color: var(--bg-color); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.copy-button, .jump-button { background-color: var(--bg-color); border: 1px solid var(--border-color); padding: 0.5rem; border-radius: 4px; cursor: pointer; transition: all 0.3s ease; color: var(--text-color); text-decoration: none; }
|
||||||
|
.copy-button:hover, .jump-button:hover { background-color: var(--primary-color); color: white; }
|
||||||
|
|
||||||
|
/* 结果提示 */
|
||||||
|
.results-info, .no-results { text-align: center; padding: 1rem; color: var(--muted-color); }
|
||||||
|
.no-results { padding: 3rem 1rem; }
|
||||||
|
.no-results h3 { color: var(--muted-color); }
|
||||||
|
.no-results p { margin: 1rem 0; }
|
||||||
|
.no-results a { color: var(--primary-color); text-decoration: none; }
|
||||||
|
.no-results a:hover { text-decoration: underline; }
|
||||||
|
|
||||||
|
/* 复制提示和回到顶部 */
|
||||||
|
.copy-notification, .back-to-top { position: fixed; bottom: 20px; right: 20px; background-color: var(--notification-bg); color: var(--notification-text); padding: 0.8rem 1.2rem; border-radius: 4px; box-shadow: 0 2px 10px rgba(0,0,0,0.2); opacity: 0; transform: translateY(20px); transition: all 0.3s ease; pointer-events: none; z-index: 999; }
|
||||||
|
.copy-notification.show, .back-to-top.show { opacity: 1; transform: translateY(0); pointer-events: auto; }
|
||||||
|
.back-to-top { width: 50px; height: 50px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 1.5rem; cursor: pointer; }
|
||||||
|
|
||||||
|
/* 移动端适配 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.progress-container { order: 2; width: 100%; }
|
||||||
|
.volume-control { margin-top: 0.5rem; width: 100%; justify-content: center; order: 3; }
|
||||||
|
.share-section { flex-direction: column; align-items: stretch; }
|
||||||
|
.share-link-container { width: 100%; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="app-container">
|
||||||
|
<div class="top-bar">
|
||||||
|
<a href="index.php" class="back-link"><i class="fas fa-arrow-left"></i> 返回主页</a>
|
||||||
|
<h1 class="site-title">音乐搜索</h1>
|
||||||
|
<div style="display: flex; gap: 10px;">
|
||||||
|
<button class="theme-toggle" id="theme-toggle"><i class="fas fa-moon"></i><span>暗黑模式</span></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main-content">
|
||||||
|
<div class="search-container">
|
||||||
|
<form id="search-form" action="sou.php" method="get">
|
||||||
|
<input type="text" id="search-input" name="s" placeholder="搜索音乐名称或歌手..." value="<?php echo htmlspecialchars($searchTerm); ?>">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (!empty($searchTerm)): ?>
|
||||||
|
<div class="results-info">
|
||||||
|
<p>为您找到 "<?php echo htmlspecialchars($searchTerm); ?>" 的相关结果,共 <?php echo count($searchResults); ?> 条。</p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div id="music-list-container">
|
||||||
|
<?php if (!empty($searchResults)): ?>
|
||||||
|
<?php foreach ($searchResults as $music): ?>
|
||||||
|
<?php
|
||||||
|
$shareUrl = $currentPageUrl . "?s=" . urlencode($searchTerm) . "&play=" . $music['id'];
|
||||||
|
$categoryConfig = $categories[$music['category']];
|
||||||
|
?>
|
||||||
|
<div class="music-item" data-category="<?php echo $music['category']; ?>" data-id="<?php echo $music['id']; ?>">
|
||||||
|
<span class="category-tag" style="background-color: <?php echo $categoryConfig['color']; ?>; color: <?php echo $categoryConfig['text_color']; ?>"><?php echo $categoryConfig['name']; ?></span>
|
||||||
|
<h2><?php echo htmlspecialchars($music['title']); ?></h2>
|
||||||
|
<p class="artist-info">作者:<?php echo htmlspecialchars($music['artist']); ?></p>
|
||||||
|
|
||||||
|
<div class="custom-audio-player">
|
||||||
|
<div class="audio-controls">
|
||||||
|
<button class="audio-button play-pause" data-audio="<?php echo $music['id']; ?>">▶</button>
|
||||||
|
<button class="audio-button loop" data-audio="<?php echo $music['id']; ?>">🔄</button>
|
||||||
|
</div>
|
||||||
|
<div class="progress-container">
|
||||||
|
<div class="audio-progress" data-audio="<?php echo $music['id']; ?>">
|
||||||
|
<div class="progress-fill" data-audio="<?php echo $music['id']; ?>"></div>
|
||||||
|
<div class="progress-handle" data-audio="<?php echo $music['id']; ?>"></div>
|
||||||
|
</div>
|
||||||
|
<span class="time-display current-time" data-audio="<?php echo $music['id']; ?>">0:00</span>
|
||||||
|
<span class="time-separator">/</span>
|
||||||
|
<span class="time-display total-time" data-audio="<?php echo $music['id']; ?>"><?php echo $music['duration']; ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="volume-control" data-audio="<?php echo $music['id']; ?>">
|
||||||
|
<button class="volume-button" data-audio="<?php echo $music['id']; ?>">🔊</button>
|
||||||
|
<input type="range" class="volume-slider" data-audio="<?php echo $music['id']; ?>" min="0" max="1" step="0.05" value="1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<audio id="<?php echo $music['id']; ?>" class="audio-element" src="<?php echo htmlspecialchars($music['mp3']); ?>" type="audio/mpeg">
|
||||||
|
您的浏览器不支持音频播放
|
||||||
|
</audio>
|
||||||
|
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe
|
||||||
|
src="https://player.bilibili.com/player.html?bvid=<?php echo $music['bvid']; ?>&page=1"
|
||||||
|
allowfullscreen="true"
|
||||||
|
loading="lazy">
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="download-section">
|
||||||
|
<p>下载:
|
||||||
|
<a href="<?php echo htmlspecialchars($music['mp3']); ?>" class="download-link" download>点我下载</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分享功能 -->
|
||||||
|
<div class="share-section">
|
||||||
|
<div class="share-link-container">
|
||||||
|
<input type="text" class="share-link" value="<?php echo htmlspecialchars($shareUrl); ?>" readonly>
|
||||||
|
<button class="copy-button" data-audio="<?php echo $music['id']; ?>" title="复制链接">
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
<a href="<?php echo htmlspecialchars($shareUrl); ?>"
|
||||||
|
class="jump-button"
|
||||||
|
target="_blank"
|
||||||
|
title="在新窗口打开">
|
||||||
|
<i class="fas fa-external-link-alt"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php elseif (!empty($searchTerm)): ?>
|
||||||
|
<!-- 没有搜索结果时显示 -->
|
||||||
|
<div class="no-results">
|
||||||
|
<h3>未找到相关结果</h3>
|
||||||
|
<p>没有找到与 "<?php echo htmlspecialchars($searchTerm); ?>" 相关的音乐</p>
|
||||||
|
<p>请尝试其他关键词或<a href="index.php">浏览全部音乐</a></p>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<!-- 没有输入搜索词时显示 -->
|
||||||
|
<div class="no-results">
|
||||||
|
<h3>请输入搜索关键词</h3>
|
||||||
|
<p>在上方搜索框中输入音乐名称或歌手名进行搜索</p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 复制成功提示 -->
|
||||||
|
<div class="copy-notification" id="copy-notification">
|
||||||
|
链接已复制到剪贴板!
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 回到顶部按钮 -->
|
||||||
|
<div class="back-to-top" id="back-to-top">
|
||||||
|
<i class="fas fa-arrow-up"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 核心功能代码
|
||||||
|
(function(d, w) {
|
||||||
|
// 工具函数简写
|
||||||
|
var q = d.querySelector.bind(d);
|
||||||
|
var qa = d.querySelectorAll.bind(d);
|
||||||
|
var addEvt = function(el, evt, fn) {
|
||||||
|
if (el.addEventListener) el.addEventListener(evt, fn);
|
||||||
|
else if (el.attachEvent) el.attachEvent('on' + evt, fn);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化时间(秒 -> 分:秒)
|
||||||
|
function formatTime(seconds) {
|
||||||
|
if (isNaN(seconds)) return "0:00";
|
||||||
|
var minutes = Math.floor(seconds / 60);
|
||||||
|
var secs = Math.floor(seconds % 60);
|
||||||
|
return minutes + ":" + (secs < 10 ? "0" + secs : secs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换播放/暂停状态
|
||||||
|
function togglePlayback(audio, playBtn) {
|
||||||
|
if (audio.paused) {
|
||||||
|
// 暂停其他正在播放的音频
|
||||||
|
qa('.audio-element').forEach(function(otherAudio) {
|
||||||
|
if (otherAudio !== audio && !otherAudio.paused) {
|
||||||
|
otherAudio.pause();
|
||||||
|
q('.play-pause[data-audio="' + otherAudio.id + '"]').textContent = '▶';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
audio.play();
|
||||||
|
playBtn.textContent = '⏸';
|
||||||
|
} else {
|
||||||
|
audio.pause();
|
||||||
|
playBtn.textContent = '▶';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化暗黑模式
|
||||||
|
function initDarkMode() {
|
||||||
|
const themeToggle = q('#theme-toggle');
|
||||||
|
const themeIcon = themeToggle.querySelector('i');
|
||||||
|
const themeText = themeToggle.querySelector('span');
|
||||||
|
|
||||||
|
// 检查用户偏好或本地存储
|
||||||
|
if (localStorage.getItem('theme') === 'dark' ||
|
||||||
|
(!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||||
|
d.body.classList.add('dark-mode');
|
||||||
|
themeIcon.classList.remove('fa-moon');
|
||||||
|
themeIcon.classList.add('fa-sun');
|
||||||
|
themeText.textContent = '明亮模式';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定切换事件
|
||||||
|
addEvt(themeToggle, 'click', function() {
|
||||||
|
d.body.classList.toggle('dark-mode');
|
||||||
|
|
||||||
|
if (d.body.classList.contains('dark-mode')) {
|
||||||
|
themeIcon.classList.remove('fa-moon');
|
||||||
|
themeIcon.classList.add('fa-sun');
|
||||||
|
themeText.textContent = '明亮模式';
|
||||||
|
localStorage.setItem('theme', 'dark');
|
||||||
|
} else {
|
||||||
|
themeIcon.classList.remove('fa-sun');
|
||||||
|
themeIcon.classList.add('fa-moon');
|
||||||
|
themeText.textContent = '暗黑模式';
|
||||||
|
localStorage.setItem('theme', 'light');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 播放进度记忆功能
|
||||||
|
function initPlaybackPositionMemory() {
|
||||||
|
// 保存播放进度
|
||||||
|
window.savePlaybackPosition = function(songId, position) {
|
||||||
|
const positions = JSON.parse(localStorage.getItem('playbackPositions') || '{}');
|
||||||
|
positions[songId] = {
|
||||||
|
position: position,
|
||||||
|
timestamp: new Date().getTime()
|
||||||
|
};
|
||||||
|
localStorage.setItem('playbackPositions', JSON.stringify(positions));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 恢复播放进度
|
||||||
|
window.restorePlaybackPosition = function(songId) {
|
||||||
|
const positions = JSON.parse(localStorage.getItem('playbackPositions') || '{}');
|
||||||
|
return positions[songId] ? positions[songId].position : 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化播放器
|
||||||
|
function initAudioPlayers() {
|
||||||
|
const audios = qa('.audio-element');
|
||||||
|
audios.forEach(function(audio) {
|
||||||
|
const id = audio.id;
|
||||||
|
const playBtn = q('.play-pause[data-audio="' + id + '"]');
|
||||||
|
const loopBtn = q('.loop[data-audio="' + id + '"]');
|
||||||
|
const progressBar = q('.audio-progress[data-audio="' + id + '"]');
|
||||||
|
const progressFill = q('.progress-fill[data-audio="' + id + '"]');
|
||||||
|
const progressHandle = q('.progress-handle[data-audio="' + id + '"]');
|
||||||
|
const currentTime = q('.current-time[data-audio="' + id + '"]');
|
||||||
|
const totalTime = q('.total-time[data-audio="' + id + '"]');
|
||||||
|
const volumeBtn = q('.volume-button[data-audio="' + id + '"]');
|
||||||
|
const volumeSlider = q('.volume-slider[data-audio="' + id + '"]');
|
||||||
|
|
||||||
|
// 获取歌曲信息
|
||||||
|
const musicItem = audio.closest('.music-item');
|
||||||
|
const songTitle = musicItem.querySelector('h2').textContent;
|
||||||
|
const artist = musicItem.querySelector('.artist-info').textContent.replace('作者:', '');
|
||||||
|
|
||||||
|
let originalVolume = 1;
|
||||||
|
let isDragging = false;
|
||||||
|
|
||||||
|
// 音频加载完成 - 恢复上次播放进度
|
||||||
|
addEvt(audio, 'loadedmetadata', function() {
|
||||||
|
totalTime.textContent = formatTime(audio.duration);
|
||||||
|
audio.volume = volumeSlider.value;
|
||||||
|
|
||||||
|
// 恢复播放进度
|
||||||
|
const savedPosition = restorePlaybackPosition(id);
|
||||||
|
if (savedPosition > 0 && savedPosition < audio.duration) {
|
||||||
|
audio.currentTime = savedPosition;
|
||||||
|
|
||||||
|
// 更新UI
|
||||||
|
const progress = (savedPosition / audio.duration) * 100;
|
||||||
|
progressFill.style.width = progress + '%';
|
||||||
|
progressHandle.style.left = progress + '%';
|
||||||
|
currentTime.textContent = formatTime(savedPosition);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 播放/暂停
|
||||||
|
addEvt(playBtn, 'click', function() {
|
||||||
|
togglePlayback(audio, playBtn);
|
||||||
|
// 添加到播放历史(复用首页的存储逻辑)
|
||||||
|
addToHistory(id, songTitle, artist);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 循环播放
|
||||||
|
addEvt(loopBtn, 'click', function() {
|
||||||
|
audio.loop = !audio.loop;
|
||||||
|
loopBtn.classList.toggle('active', audio.loop);
|
||||||
|
if (audio.loop) {
|
||||||
|
loopBtn.style.transform = 'scale(1.1)';
|
||||||
|
setTimeout(function() {
|
||||||
|
loopBtn.style.transform = 'scale(1)';
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新进度 - 并保存播放进度
|
||||||
|
addEvt(audio, 'timeupdate', function() {
|
||||||
|
if (!isDragging) {
|
||||||
|
const progress = (audio.currentTime / audio.duration) * 100;
|
||||||
|
progressFill.style.width = progress + '%';
|
||||||
|
progressHandle.style.left = progress + '%';
|
||||||
|
currentTime.textContent = formatTime(audio.currentTime);
|
||||||
|
|
||||||
|
// 定期保存播放进度
|
||||||
|
if (audio.currentTime > 0 &&
|
||||||
|
(audio.currentTime % 30 < 0.1 ||
|
||||||
|
Math.abs(audio.currentTime - (audio.duration * 0.9)) < 0.5)) {
|
||||||
|
savePlaybackPosition(id, audio.currentTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 播放结束时保存最终进度
|
||||||
|
addEvt(audio, 'ended', function() {
|
||||||
|
savePlaybackPosition(id, 0); // 播放结束后重置进度
|
||||||
|
playBtn.textContent = '▶';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 进度条拖动
|
||||||
|
const startDrag = function(e) {
|
||||||
|
isDragging = true;
|
||||||
|
progressHandle.classList.add('dragging');
|
||||||
|
updateProgress(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateProgress = function(e) {
|
||||||
|
if (!audio.duration) return;
|
||||||
|
|
||||||
|
// 获取进度条的位置和宽度
|
||||||
|
const rect = progressBar.getBoundingClientRect();
|
||||||
|
let pos = (e.clientX - rect.left) / rect.width;
|
||||||
|
pos = Math.max(0, Math.min(1, pos)); // 限制在0-1之间
|
||||||
|
|
||||||
|
// 更新UI
|
||||||
|
const percentage = pos * 100;
|
||||||
|
progressFill.style.width = percentage + '%';
|
||||||
|
progressHandle.style.left = percentage + '%';
|
||||||
|
currentTime.textContent = formatTime(pos * audio.duration);
|
||||||
|
};
|
||||||
|
|
||||||
|
const endDrag = function() {
|
||||||
|
if (!isDragging) return;
|
||||||
|
|
||||||
|
isDragging = false;
|
||||||
|
progressHandle.classList.remove('dragging');
|
||||||
|
|
||||||
|
// 设置音频位置
|
||||||
|
const pos = parseFloat(progressFill.style.width) / 100;
|
||||||
|
audio.currentTime = pos * audio.duration;
|
||||||
|
savePlaybackPosition(id, audio.currentTime);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 进度条事件
|
||||||
|
addEvt(progressBar, 'click', updateProgress);
|
||||||
|
addEvt(progressHandle, 'mousedown', startDrag);
|
||||||
|
addEvt(d, 'mousemove', function(e) { if (isDragging) updateProgress(e); });
|
||||||
|
addEvt(d, 'mouseup', endDrag);
|
||||||
|
addEvt(d, 'mouseleave', endDrag);
|
||||||
|
|
||||||
|
// 移动端触摸事件
|
||||||
|
addEvt(progressBar, 'touchstart', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
startDrag(e.touches[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
addEvt(d, 'touchmove', function(e) {
|
||||||
|
if (isDragging) {
|
||||||
|
e.preventDefault();
|
||||||
|
updateProgress(e.touches[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addEvt(d, 'touchend', endDrag);
|
||||||
|
|
||||||
|
// 音量控制
|
||||||
|
addEvt(volumeBtn, 'click', function() {
|
||||||
|
if (audio.volume > 0) {
|
||||||
|
originalVolume = audio.volume;
|
||||||
|
audio.volume = 0;
|
||||||
|
volumeBtn.textContent = '🔇';
|
||||||
|
volumeSlider.value = 0;
|
||||||
|
} else {
|
||||||
|
audio.volume = originalVolume;
|
||||||
|
volumeBtn.textContent = '🔊';
|
||||||
|
volumeSlider.value = originalVolume;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addEvt(volumeSlider, 'input', function() {
|
||||||
|
audio.volume = this.value;
|
||||||
|
originalVolume = this.value;
|
||||||
|
volumeBtn.textContent = this.value > 0 ? '🔊' : '🔇';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化复制功能
|
||||||
|
function initCopyFunctionality() {
|
||||||
|
const copyButtons = qa('.copy-button');
|
||||||
|
const notification = q('#copy-notification');
|
||||||
|
|
||||||
|
copyButtons.forEach(function(btn) {
|
||||||
|
addEvt(btn, 'click', function() {
|
||||||
|
const audioId = this.getAttribute('data-audio');
|
||||||
|
const shareLink = this.parentElement.querySelector('.share-link');
|
||||||
|
|
||||||
|
if (shareLink) {
|
||||||
|
shareLink.select();
|
||||||
|
d.execCommand('copy');
|
||||||
|
|
||||||
|
// 显示通知
|
||||||
|
notification.classList.add('show');
|
||||||
|
setTimeout(function() {
|
||||||
|
notification.classList.remove('show');
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化回到顶部按钮
|
||||||
|
function initBackToTop() {
|
||||||
|
const backToTopBtn = q('#back-to-top');
|
||||||
|
|
||||||
|
addEvt(w, 'scroll', function() {
|
||||||
|
if (w.pageYOffset > 300) {
|
||||||
|
backToTopBtn.classList.add('show');
|
||||||
|
} else {
|
||||||
|
backToTopBtn.classList.remove('show');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addEvt(backToTopBtn, 'click', function() {
|
||||||
|
w.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加到播放历史(与首页保持一致的存储格式)
|
||||||
|
window.addToHistory = function(songId, songTitle, artist) {
|
||||||
|
let history = JSON.parse(localStorage.getItem('playHistory') || '[]');
|
||||||
|
|
||||||
|
// 移除已存在的相同歌曲(避免重复)
|
||||||
|
history = history.filter(item => item.id !== songId);
|
||||||
|
|
||||||
|
// 添加新记录到最前面
|
||||||
|
history.unshift({
|
||||||
|
id: songId,
|
||||||
|
title: songTitle,
|
||||||
|
artist: artist,
|
||||||
|
timestamp: new Date().getTime()
|
||||||
|
});
|
||||||
|
|
||||||
|
// 限制历史记录数量
|
||||||
|
if (history.length > 20) {
|
||||||
|
history = history.slice(0, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem('playHistory', JSON.stringify(history));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 检查URL参数,自动播放指定音乐
|
||||||
|
function autoPlayFromUrl() {
|
||||||
|
const params = new URLSearchParams(w.location.search);
|
||||||
|
const playId = params.get('play');
|
||||||
|
|
||||||
|
if (playId) {
|
||||||
|
const audio = q('#' + playId);
|
||||||
|
const playBtn = q('.play-pause[data-audio="' + playId + '"]');
|
||||||
|
|
||||||
|
if (audio && playBtn) {
|
||||||
|
// 滚动到音乐项
|
||||||
|
const musicItem = audio.closest('.music-item');
|
||||||
|
if (musicItem) {
|
||||||
|
musicItem.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 播放音乐
|
||||||
|
setTimeout(function() {
|
||||||
|
if (audio.paused) {
|
||||||
|
togglePlayback(audio, playBtn);
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化所有功能
|
||||||
|
function initAll() {
|
||||||
|
initDarkMode();
|
||||||
|
initPlaybackPositionMemory();
|
||||||
|
initAudioPlayers();
|
||||||
|
initCopyFunctionality();
|
||||||
|
initBackToTop();
|
||||||
|
autoPlayFromUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载完成后初始化
|
||||||
|
addEvt(w, 'load', initAll);
|
||||||
|
})(document, window);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
Reference in New Issue
Block a user