Files
MovieLog_Server/cinemas.php
T
laoyang dd957d45bd refactor: 拆分 config.php 为 lib/ 模块,消除页面层重复代码
纯结构性重构,不改变任何功能。

- config.php 由 739 行缩减为 42 行,改为纯引导入口;新增 ROOT_DIR 常量
  统一拼接绝对路径(lib/ 内模块的 __DIR__ 会指向 lib/,必须替换)
- 新增 lib/ 五个模块 db.php / helpers.php / auth.php / movies.php / stats.php,
  由 config.php 按 db → helpers → auth → movies → stats 顺序加载
- add.php、edit.php 各自约 80 行重复的 POST 处理改为调用
  resolveCinemaId / resolvePersonIds / linkViewers / uploadImageFile /
  saveBase64Image / uploadMultiplePhotos / deleteStoredFile
- cinemas.php、persons.php 中重复 4 次的颜色校验改用 normalizeHexColor()
- toggleNewViewer / removeViewerRow / toggleNewCinema 下沉至 js/main.js,
  add.php 中的 removeViewer 统一更名为 removeViewerRow
- 删除死代码:imageUrl()(全项目无调用);cinemas.php 配色数组中本就会被
  array_slice 丢弃的 extra 颜色逻辑,简化为单条扁平 50 元素数组
- README 目录结构补充 lib/ 说明

验证方式:以便携 PHP 8.4 搭建 A/B 回归环境,播种固定数据后遍历 47 个
GET/POST 端点录制「状态码 + 响应体」。重构前后 42 个逐字节相同、2 个仅
上传文件名时间戳不同、3 个差异均为内联脚本下沉(已单独校验函数体逐字符
等价,剥离脚本后 HTML 标记完全一致)。24 个 PHP 文件语法检查全部通过。
2026-09-21 12:56:30 +08:00

451 lines
16 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
// ============================================
// 文件: cinemas.php (影院管理 - 含50色)
// ============================================
require_once 'config.php';
checkLogin();
$admin = getCurrentAdmin();
$db = getDB();
$message = '';
$error = '';
// ---------- 处理添加 ----------
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'add') {
$place = trim($_POST['place'] ?? '');
$name = trim($_POST['name'] ?? '');
$address = trim($_POST['address'] ?? '');
$color = normalizeHexColor($_POST['color'] ?? '#888888');
if (empty($name)) {
$error = '影院名称不能为空';
} else {
$stmt = $db->prepare("INSERT INTO cinemas (place, name, address, color) VALUES (?, ?, ?, ?)");
$stmt->execute([$place, $name, $address, $color]);
$message = '影院添加成功';
}
}
// ---------- 处理编辑 ----------
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'edit') {
$id = (int)$_POST['id'];
$place = trim($_POST['place'] ?? '');
$name = trim($_POST['name'] ?? '');
$address = trim($_POST['address'] ?? '');
$color = normalizeHexColor($_POST['color'] ?? '#888888');
if (empty($name)) {
$error = '影院名称不能为空';
} else {
$stmt = $db->prepare("UPDATE cinemas SET place = ?, name = ?, address = ?, color = ? WHERE id = ?");
$stmt->execute([$place, $name, $address, $color, $id]);
$message = '影院信息已更新';
}
}
// ---------- 处理删除 ----------
if (isset($_GET['action']) && $_GET['action'] === 'delete' && isset($_GET['id'])) {
$id = (int)$_GET['id'];
// 检查是否有电影关联
$stmt = $db->prepare("SELECT COUNT(*) FROM movies WHERE cinema_id = ?");
$stmt->execute([$id]);
$count = $stmt->fetchColumn();
if ($count > 0) {
$confirm = isset($_GET['confirm']) ? $_GET['confirm'] : '';
if ($confirm !== 'yes') {
$error = "该影院关联了 {$count} 部电影,删除后这些电影的影院信息将置空。确定删除?请 <a href='?action=delete&id={$id}&confirm=yes' onclick=\"return confirm('确认删除?')\">点击确认删除</a>";
} else {
$stmt = $db->prepare("DELETE FROM cinemas WHERE id = ?");
$stmt->execute([$id]);
$message = '影院已删除,关联电影已置空影院信息';
}
} else {
$stmt = $db->prepare("DELETE FROM cinemas WHERE id = ?");
$stmt->execute([$id]);
$message = '影院已删除';
}
}
// ---------- 获取列表 ----------
$cinemas = getCinemas();
// ---------- 获取编辑数据 ----------
$edit_data = null;
if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id'])) {
$id = (int)$_GET['id'];
$stmt = $db->prepare("SELECT * FROM cinemas WHERE id = ?");
$stmt->execute([$id]);
$edit_data = $stmt->fetch();
if (!$edit_data) {
header('Location: cinemas.php');
exit;
}
}
// ---------- 预设 50 色 ----------
$preset_colors = [
'#e74c3c', '#c0392b', '#e67e22', '#d35400', '#f39c12',
'#f1c40f', '#f7dc6f', '#f5b041', '#f4d03f', '#f9e79f',
'#f8c471', '#2ecc71', '#27ae60', '#1abc9c', '#16a085',
'#58d68d', '#48c9b0', '#45b39d', '#82e0aa', '#a3e4d7',
'#3498db', '#2980b9', '#2e86c1', '#5dade2', '#85c1e9',
'#5499c7', '#aed6f1', '#9b59b6', '#8e44ad', '#af7ac5',
'#bb8fce', '#d2b4de', '#e84393', '#fd79a8', '#e91e63',
'#f06292', '#95a5a6', '#7f8c8d', '#bdc3c7', '#d5dbdb',
'#aab7b8', '#99a3a4', '#a67c52', '#8d6e63', '#d4a574',
'#c9a87c', '#00b894', '#0984e3', '#6c5ce7', '#fd79a8',
];
?>
<!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>
.container {
max-width: 1000px;
margin: 0 auto;
padding: 20px;
}
.card {
background: var(--card-bg);
border-radius: 16px;
padding: 24px 28px;
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[type="text"],
.form-group input[type="color"] {
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 {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 4px rgba(230,126,34,0.12);
}
.form-group input[type="color"] {
padding: 4px;
height: 44px;
width: 60px;
cursor: pointer;
}
.form-row {
display: flex;
gap: 16px;
flex-wrap: wrap;
}
.form-row .form-group {
flex: 1;
min-width: 180px;
}
.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;
}
.message-box {
padding: 10px 14px;
border-radius: 8px;
margin-bottom: 16px;
font-size: 14px;
}
.message-box.success {
background: rgba(39,174,96,0.08);
border: 1px solid rgba(39,174,96,0.2);
color: #27ae60;
}
.message-box.error {
background: rgba(231,76,60,0.08);
border: 1px solid rgba(231,76,60,0.2);
color: #e74c3c;
}
.table-wrap {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
table th {
text-align: left;
padding: 10px 8px;
border-bottom: 2px solid var(--border-color);
color: var(--text-secondary);
font-weight: 600;
}
table td {
padding: 10px 8px;
border-bottom: 1px solid var(--border-color);
}
table tr:hover td {
background: var(--bg-secondary);
}
.actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.empty-row {
text-align: center;
color: var(--text-secondary);
padding: 20px 0;
}
.color-preview {
display: inline-block;
width: 14px;
height: 14px;
border-radius: 3px;
margin-right: 6px;
vertical-align: middle;
border: 1px solid rgba(0,0,0,0.1);
}
.preset-palette {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 8px;
}
.preset-color {
display: inline-block;
width: 30px;
height: 30px;
border-radius: 50%;
cursor: pointer;
border: 2px solid transparent;
transition: border-color 0.2s, transform 0.2s;
}
.preset-color:hover {
transform: scale(1.1);
}
.preset-color.active {
border-color: #333;
}
.color-input-group {
display: flex;
align-items: center;
gap: 12px;
}
.color-input-group input[type="color"] {
flex-shrink: 0;
}
.color-input-group .color-hex {
font-size: 14px;
color: var(--text-secondary);
font-family: monospace;
}
@media (max-width: 600px) {
.card {
padding: 16px;
}
.form-row {
flex-direction: column;
}
.actions {
flex-wrap: wrap;
}
}
</style>
</head>
<body>
<?php include 'nav.php'; ?>
<main class="main-content">
<div class="container">
<?php if ($message): ?>
<div class="message-box success"><?= h($message) ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="message-box error"><?= $error ?></div>
<?php endif; ?>
<!-- 添加/编辑表单 -->
<div class="card">
<h2><?= $edit_data ? '✏️ 编辑影院' : ' 添加影院' ?></h2>
<p class="sub"><?= $edit_data ? '修改影院信息' : '录入新的电影院' ?></p>
<form method="POST" action="">
<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>影院名称 <span class="hint">*必填</span></label>
<input type="text" name="name" value="<?= $edit_data ? h($edit_data['name']) : '' ?>" placeholder="如:万达影城(IMAX店)" required>
</div>
<div class="form-group">
<label>所在地方</label>
<input type="text" name="place" value="<?= $edit_data ? h($edit_data['place']) : '' ?>" placeholder="如:万达广场">
</div>
<div class="form-group">
<label>详细地址</label>
<input type="text" name="address" value="<?= $edit_data ? h($edit_data['address']) : '' ?>" placeholder="详细街道地址">
</div>
</div>
<!-- 颜色选择 -->
<div class="form-group">
<label>主题颜色</label>
<div class="color-input-group">
<input type="color" name="color" id="colorPicker" value="<?= $edit_data ? h($edit_data['color']) : '#888888' ?>">
<span class="color-hex" id="colorHex"><?= $edit_data ? h($edit_data['color']) : '#888888' ?></span>
</div>
<div class="preset-palette">
<?php foreach ($preset_colors as $pc): ?>
<span class="preset-color" data-color="<?= $pc ?>" style="background: <?= $pc ?>;" onclick="setColor('<?= $pc ?>')"></span>
<?php endforeach; ?>
</div>
</div>
<div style="margin-top: 12px;">
<button type="submit" class="btn btn-primary">💾 保存</button>
<?php if ($edit_data): ?>
<a href="cinemas.php" class="btn btn-outline">取消编辑</a>
<?php endif; ?>
</div>
</form>
</div>
<!-- 影院列表 -->
<div class="card">
<h2>🏛️ 影院列表</h2>
<p class="sub">共 <?= count($cinemas) ?> 家影院</p>
<div class="table-wrap">
<?php if (count($cinemas) > 0): ?>
<table>
<thead>
<tr>
<th>名称</th>
<th>所在地方</th>
<th>详细地址</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php foreach ($cinemas as $c): ?>
<tr>
<td>
<span class="color-preview" style="background: <?= h($c['color'] ?: '#888888') ?>;"></span>
<strong><?= h($c['name']) ?></strong>
</td>
<td><?= h($c['place']) ?: '-' ?></td>
<td><?= h($c['address']) ?: '-' ?></td>
<td>
<div class="actions">
<a href="?action=edit&id=<?= $c['id'] ?>" class="btn btn-outline btn-sm">✏️ 编辑</a>
<a href="?action=delete&id=<?= $c['id'] ?>" class="btn btn-danger btn-sm" onclick="return confirm('确定删除「<?= h($c['name']) ?>」吗?关联电影将置空影院信息。')">🗑️ 删除</a>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<div class="empty-row">暂无影院,请添加</div>
<?php endif; ?>
</div>
</div>
</div>
</main>
<script>
function setColor(color) {
document.getElementById('colorPicker').value = color;
document.getElementById('colorHex').textContent = color;
// 高亮当前选中的预设色块
document.querySelectorAll('.preset-color').forEach(el => {
el.classList.remove('active');
});
document.querySelector(`.preset-color[data-color="${color}"]`).classList.add('active');
}
// 当颜色选择器手动变化时,更新hex显示和激活状态
document.getElementById('colorPicker').addEventListener('input', function(e) {
var color = this.value;
document.getElementById('colorHex').textContent = color;
document.querySelectorAll('.preset-color').forEach(el => {
el.classList.remove('active');
if (el.dataset.color === color) {
el.classList.add('active');
}
});
});
// 页面加载时,高亮当前颜色
document.addEventListener('DOMContentLoaded', function() {
var currentColor = document.getElementById('colorPicker').value;
if (currentColor) {
document.querySelectorAll('.preset-color').forEach(el => {
if (el.dataset.color === currentColor) {
el.classList.add('active');
}
});
document.getElementById('colorHex').textContent = currentColor;
}
});
</script>
</body>
</html>