Files
MovieLog_Server/users.php
T
laoyang d7c6ac2559 feat: 完善电影记录系统源码与项目文档
功能模块:
- 观影记录:列表(瀑布流)/ 添加 / 编辑 / 详情 / 删除,删除时同步清理图片文件
- 首页快捷搜索 + 高级搜索(片名、影院、观影人、日期区间组合筛选)
- 影院管理:三段式信息 + 50 种预设配色,删除已引用影院时二次确认
- 观影人管理:头像裁剪上传 + 专属配色,已引用者禁止删除
- 图片处理:Cropper.js 裁剪票根/海报/观影照片/头像,多图上传,首字母 SVG 头像兜底
- OMDb 自动获取影片信息(fetch_movie.php 代理,返回 JSON)
- CSV 批量导入:UTF-8/GBK 自动识别,影院去重,事务保护,逐行错误报告
- 统计仪表板:总览 + 按影院/观影人/年份/近 12 个月排行
- 年度报告:按账号关联的观影人视角生成,含 Chart.js 月度与星期分布图
- 多用户:账号增删改、账号与观影人绑定、修改自己的用户名与密码
- 登录鉴权:Session + password_hash,全站页面登录校验

文档:
- 重写 README:功能特性、技术栈、目录结构、数据库结构、部署与使用指南、接口说明、CSV 格式、安全注意事项、已知限制
- 新增 .gitignore,排除 db/ 与 uploads/ 等运行时数据
2026-09-21 08:16:25 +08:00

442 lines
17 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// ============================================
// 文件: users.php (完整版,含编辑功能)
// ============================================
require_once 'config.php';
checkLogin();
$admin = getCurrentAdmin();
$db = getDB();
// 获取所有观影人
$stmt = $db->query("SELECT * FROM persons ORDER BY name");
$all_persons = $stmt->fetchAll();
$message = '';
$error = '';
// 处理关联观影人(当前用户)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'link_person') {
$admin_id = (int)$_POST['admin_id'];
$person_id = !empty($_POST['person_id']) ? (int)$_POST['person_id'] : null;
if ($admin_id != $_SESSION['admin_id']) {
$error = '只能修改自己的关联';
} else {
if (updateAdminPersonId($admin_id, $person_id)) {
$message = '关联观影人更新成功';
$_SESSION['admin_id'] = $admin_id;
$admin = getCurrentAdmin();
} else {
$error = '更新失败';
}
}
}
// 处理添加用户
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'add') {
$new_user = trim($_POST['new_username'] ?? '');
$new_pass = $_POST['new_password'] ?? '';
$confirm = $_POST['confirm_password'] ?? '';
$person_id = !empty($_POST['person_id']) ? (int)$_POST['person_id'] : null;
if (empty($new_user)) {
$error = '用户名不能为空';
} elseif (strlen($new_pass) < 6) {
$error = '密码至少6位';
} elseif ($new_pass !== $confirm) {
$error = '两次密码不一致';
} else {
if (addAdmin($new_user, $new_pass, null, $person_id)) {
$message = "用户 {$new_user} 添加成功";
} else {
$error = '用户名已存在';
}
}
}
// 处理编辑用户
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'edit') {
$edit_id = (int)$_POST['id'];
$username = trim($_POST['username'] ?? '');
$new_password = $_POST['new_password'] ?? '';
$confirm_password = $_POST['confirm_password'] ?? '';
$person_id = !empty($_POST['person_id']) ? (int)$_POST['person_id'] : null;
if (empty($username)) {
$error = '用户名不能为空';
} else {
// 检查用户名是否已被其他用户使用
$stmt = $db->prepare("SELECT COUNT(*) FROM admins WHERE username = ? AND id != ?");
$stmt->execute([$username, $edit_id]);
if ($stmt->fetchColumn() > 0) {
$error = '用户名已被占用';
} else {
// 构建更新语句
$sql = "UPDATE admins SET username = ?, person_id = ?";
$params = [$username, $person_id];
if (!empty($new_password)) {
if (strlen($new_password) < 6) {
$error = '密码至少6位';
} elseif ($new_password !== $confirm_password) {
$error = '两次密码不一致';
} else {
$hashed = password_hash($new_password, PASSWORD_DEFAULT);
$sql .= ", password = ?";
$params[] = $hashed;
}
}
if (empty($error)) {
$sql .= " WHERE id = ?";
$params[] = $edit_id;
$stmt = $db->prepare($sql);
if ($stmt->execute($params)) {
$message = "用户 {$username} 更新成功";
// 如果编辑的是自己,刷新 session 信息
if ($edit_id == $_SESSION['admin_id']) {
$_SESSION['admin_id'] = $edit_id;
$admin = getCurrentAdmin();
}
} else {
$error = '更新失败';
}
}
}
}
}
// 处理删除用户
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'delete') {
$delete_id = (int)$_POST['delete_id'];
if (deleteAdmin($delete_id)) {
$message = '用户已删除';
} else {
$error = '无法删除该用户(可能是最后一个管理员或您自己)';
}
}
// 获取编辑数据
$edit_data = null;
if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id'])) {
$edit_id = (int)$_GET['id'];
$stmt = $db->prepare("SELECT * FROM admins WHERE id = ?");
$stmt->execute([$edit_id]);
$edit_data = $stmt->fetch();
if (!$edit_data) {
header('Location: users.php');
exit;
}
}
$users = getAllAdmins();
?>
<!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="css/style.css">
<style>
.form-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.card {
background: var(--card-bg);
border-radius: 16px;
padding: 28px 32px;
box-shadow: var(--shadow);
margin-bottom: 24px;
}
.card h2 {
font-size: 22px;
font-weight: 700;
margin: 0 0 8px;
color: var(--text-primary);
}
.card .sub {
color: var(--text-secondary);
font-size: 14px;
margin-bottom: 16px;
}
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
font-weight: 600;
font-size: 14px;
color: var(--text-primary);
margin-bottom: 4px;
}
.form-group input, .form-group select {
width: 100%;
padding: 10px 14px;
border: 1px solid var(--border-color);
border-radius: 8px;
font-size: 15px;
box-sizing: border-box;
transition: border-color 0.2s;
}
.form-group input:focus, .form-group select:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 4px rgba(230,126,34,0.12);
}
.btn {
padding: 10px 20px;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
border: none;
cursor: pointer;
transition: var(--transition);
text-decoration: none;
display: inline-block;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: #fff;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 28px rgba(230,126,34,0.35);
}
.btn-danger {
background: #e74c3c;
color: #fff;
}
.btn-danger:hover {
background: #c0392b;
}
.btn-outline {
background: transparent;
color: var(--text-secondary);
border: 1px solid var(--border-color);
}
.btn-outline:hover {
background: var(--bg-secondary);
}
.btn-sm {
padding: 4px 12px;
font-size: 13px;
}
.user-list {
list-style: none;
padding: 0;
margin: 0;
}
.user-list li {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid var(--border-color);
flex-wrap: wrap;
gap: 8px;
}
.user-list li:last-child {
border-bottom: none;
}
.user-list .username {
font-weight: 500;
}
.user-list .badge {
background: var(--bg-secondary);
color: var(--text-secondary);
font-size: 12px;
padding: 2px 10px;
border-radius: 12px;
margin-left: 8px;
}
.user-list .badge.self {
background: var(--primary);
color: #fff;
}
.user-list .badge.has-link {
background: #2ecc71;
color: #fff;
}
.error-box {
background: rgba(231,76,60,0.08);
border: 1px solid rgba(231,76,60,0.2);
color: #e74c3c;
padding: 10px 14px;
border-radius: 8px;
margin-bottom: 16px;
font-size: 14px;
}
.success-box {
background: rgba(39,174,96,0.08);
border: 1px solid rgba(39,174,96,0.2);
color: #27ae60;
padding: 10px 14px;
border-radius: 8px;
margin-bottom: 16px;
font-size: 14px;
}
.form-row {
display: flex;
gap: 12px;
align-items: flex-end;
flex-wrap: wrap;
}
.form-row .form-group {
flex: 1;
margin-bottom: 0;
min-width: 150px;
}
.link-form {
display: flex;
gap: 12px;
align-items: center;
flex-wrap: wrap;
}
.link-form select {
flex: 1;
min-width: 150px;
}
@media (max-width: 600px) {
.form-row {
flex-direction: column;
}
.card {
padding: 20px 16px;
}
.link-form {
flex-direction: column;
align-items: stretch;
}
}
</style>
</head>
<body>
<?php include 'nav.php'; ?>
<main class="main-content">
<div class="form-container">
<!-- 当前用户关联设置 -->
<div class="card">
<h2>🔗 关联我的观影人身份</h2>
<p class="sub">设置你作为观影人的身份,用于年度报告等统计功能(排除自己)</p>
<?php if ($message): ?>
<div class="success-box"><?= h($message) ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="error-box"><?= h($error) ?></div>
<?php endif; ?>
<form method="POST" class="link-form">
<input type="hidden" name="action" value="link_person">
<input type="hidden" name="admin_id" value="<?= $_SESSION['admin_id'] ?>">
<select name="person_id">
<option value="">-- 不关联 --</option>
<?php foreach ($all_persons as $p): ?>
<option value="<?= $p['id'] ?>" <?= ($admin['person_id'] == $p['id']) ? 'selected' : '' ?>>
<?= h($p['name']) ?>
</option>
<?php endforeach; ?>
</select>
<button type="submit" class="btn btn-primary">保存关联</button>
<?php if ($admin['person_id']): ?>
<span style="font-size:13px;color:#27ae60;">✅ 当前关联:<?= h($admin['person_name'] ?? '') ?></span>
<?php else: ?>
<span style="font-size:13px;color:var(--text-secondary);">⚠️ 未关联观影人,年度报告将无法排除你自己</span>
<?php endif; ?>
</form>
</div>
<!-- 添加/编辑用户 -->
<div class="card">
<h2><?= $edit_data ? '✏️ 编辑用户' : ' 添加新管理员' ?></h2>
<p class="sub"><?= $edit_data ? '修改用户信息' : '新增一个可登录系统的账户' ?></p>
<form method="POST">
<input type="hidden" name="action" value="<?= $edit_data ? 'edit' : 'add' ?>">
<?php if ($edit_data): ?>
<input type="hidden" name="id" value="<?= $edit_data['id'] ?>">
<?php endif; ?>
<div class="form-row">
<div class="form-group">
<label>用户名</label>
<input type="text" name="<?= $edit_data ? 'username' : 'new_username' ?>"
value="<?= $edit_data ? h($edit_data['username']) : '' ?>"
placeholder="如: manager" required>
</div>
<div class="form-group">
<label><?= $edit_data ? '新密码 (留空则不修改)' : '密码 (至少6位)' ?></label>
<input type="password" name="new_password" placeholder="<?= $edit_data ? '留空则不修改' : '输入密码' ?>">
</div>
<?php if ($edit_data): ?>
<div class="form-group">
<label>确认新密码</label>
<input type="password" name="confirm_password" placeholder="再次输入新密码">
</div>
<?php endif; ?>
<div class="form-group">
<label>关联观影人</label>
<select name="person_id">
<option value="">-- 不关联 --</option>
<?php foreach ($all_persons as $p): ?>
<option value="<?= $p['id'] ?>" <?= ($edit_data && $edit_data['person_id'] == $p['id']) ? 'selected' : '' ?>>
<?= h($p['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div>
<button type="submit" class="btn btn-primary"><?= $edit_data ? '💾 保存修改' : '添加用户' ?></button>
<?php if ($edit_data): ?>
<a href="users.php" class="btn btn-outline">取消</a>
<?php endif; ?>
</div>
</div>
</form>
</div>
<!-- 用户列表 -->
<div class="card">
<h2>👥 现有管理员</h2>
<p class="sub">共 <?= count($users) ?> 个账户</p>
<?php if (count($users) > 0): ?>
<ul class="user-list">
<?php foreach ($users as $u): ?>
<li>
<span>
<span class="username"><?= h($u['username']) ?></span>
<?php if ($u['id'] == $_SESSION['admin_id']): ?>
<span class="badge self">当前登录</span>
<?php else: ?>
<span class="badge">管理员</span>
<?php endif; ?>
<?php if ($u['person_id']): ?>
<span class="badge has-link">👤 关联: <?= h($u['person_name']) ?></span>
<?php endif; ?>
</span>
<span>
<a href="?action=edit&id=<?= $u['id'] ?>" class="btn btn-outline btn-sm">✏️ 编辑</a>
<?php if ($u['id'] != $_SESSION['admin_id']): ?>
<form method="POST" style="display:inline;" onsubmit="return confirm('确定删除用户 <?= h($u['username']) ?> 吗?')">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="delete_id" value="<?= $u['id'] ?>">
<button type="submit" class="btn btn-danger btn-sm">删除</button>
</form>
<?php else: ?>
<span style="color: var(--text-secondary); font-size:13px;">不可删除自己</span>
<?php endif; ?>
</span>
</li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<p style="color: var(--text-secondary);">暂无其他管理员</p>
<?php endif; ?>
</div>
</div>
</main>
</body>
</html>