diff --git a/README.md b/README.md
index c3daff5..553d981 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@
### 图片处理
- **内置裁剪器**:基于 Cropper.js,票根 / 海报 / 观影照片 / 观影人头像均可在浏览器内裁剪后再保存,服务端接收 Base64 图片落盘。
- **多图上传**:观影照片支持一次选择多张。
-- **头像兜底**:未上传头像的观影人自动生成"首字母 + 配色"的 SVG 头像,配色由姓名哈希决定,同名同色。
+- **头像兜底**:未上传头像的观影人一律显示 `assets/default-avatar.png` 默认头像(人形剪影);若该资源缺失(例如只部署了 PHP 而漏了 `assets/`),`getAvatarUrl()` 会退化为内联 SVG 剪影 —— 保证任何情况下都不会出现"坏图"。
- 上传目录按类型分离:`uploads/posters/`、`uploads/tickets/`、`uploads/photos/`、`uploads/avatars/`。
- **破图兜底**:图片加载失败时由前端脚本替换为内联占位图,避免会话过期或权限异常时满屏破图刷屏控制台。
@@ -116,7 +116,8 @@ MovieLog_Server/
├── test_crop.php # 开发调试页:验证 Base64 图片落盘是否正常
├── css/style.css # 全局样式
├── js/main.js # 公共脚本(含各页面共用的表单联动函数、破图兜底)
-├── assets/.gitkeep # 静态占位资源目录
+├── assets/ # 静态资源
+│ └── default-avatar.png # 默认头像(未上传头像时统一使用)
├── db/movies.db # SQLite 数据库(运行时自动生成,不纳入版本控制)
└── uploads/ # 上传图片(运行时自动生成,不纳入版本控制)
├── posters/ # 海报
@@ -565,7 +566,7 @@ curl -sk -H "Host: 你的域名" -H "Cookie: PHPSESSID=xxxx" -o /dev/null \
- 暂无分页,数据量增大后首页与统计页需要补充分页或懒加载。
- `config.php` 的 `initDB()` 在每次请求都会跑一遍建表与迁移,数据量或并发上升后可改为一次性迁移脚本。
- 无「回收站」,删除电影与其图片文件不可恢复。
-- `assets/placeholder-poster.jpg`、`assets/default-avatar.png` 为代码中的兜底引用,当前仓库未包含这两个文件;由于页面在检测到 placeholder 时会改用 CSS 占位块渲染,正常使用不受影响。
+- `assets/placeholder-poster.jpg` 仍是代码中的兜底引用,**仓库未包含该文件**;页面在检测到 placeholder 时会改用 CSS 占位块渲染,正常使用不受影响。`assets/default-avatar.png` 已随仓库提供(默认头像),并在资源缺失时由 `getAvatarUrl()` 退化为内联 SVG 剪影。
- **「直连存储」模式下裁剪功能不可用**:裁剪需要读取图片像素,直连时图片来自对象存储域名,必须同时满足「桶配 CORS」与「``」两个条件才能读像素;本仓库未加 `crossorigin` 属性,因此直连模式下只能上传、不能二次裁剪。需要裁剪请改用「服务端代理」(同源,两个条件全免)。详见《图片存储与对象存储》§3③。
- **服务端代理模式下所有图片流量经服务器中转**,会占用服务器出网带宽,并给每张图增加一次服务器↔存储的往返(同地域约 10–30 ms,跨地域约 60–100 ms)。图片量大且可公开时可考虑改回直连。
- **内网 endpoint 只在服务器与桶同地域时生效**。跨地域部署下这条免费通道用不了,程序会自动跳过并走公网(详见 §4)。
diff --git a/assets/default-avatar.png b/assets/default-avatar.png
new file mode 100644
index 0000000..bbe4840
Binary files /dev/null and b/assets/default-avatar.png differ
diff --git a/lib/helpers.php b/lib/helpers.php
index 4044338..49a1b41 100644
--- a/lib/helpers.php
+++ b/lib/helpers.php
@@ -135,17 +135,40 @@ function posterUrl($path) {
}
// ===== 头像处理 =====
+
+// 默认头像 URL:未上传头像的人统一使用它。
+// 资源随仓库分发(assets/default-avatar.png)。
+// 若该文件缺失——比如只部署了 PHP 而漏了 assets/——则退化为内联 SVG 剪影,
+// 避免出现「兜底图本身就是坏图」:这正是本函数此前的问题(返回了一个并不存在的路径)。
+// 注意:这里用 is_file() 探测,路径在站点目录内,不会像站外路径那样触碰 open_basedir 限制。
+function defaultAvatarUrl() {
+ static $url = null;
+ if ($url !== null) {
+ return $url;
+ }
+ $root = defined('ROOT_DIR') ? ROOT_DIR : dirname(__DIR__);
+ if (is_file($root . '/assets/default-avatar.png')) {
+ $url = '/assets/default-avatar.png';
+ return $url;
+ }
+ // 几何比例与 assets/default-avatar.png 保持一致,两种情形视觉无差别
+ $svg = "";
+ $url = 'data:image/svg+xml,' . rawurlencode($svg);
+ return $url;
+}
+
+// 头像 URL:已上传 → 存储层 URL;未上传 → 统一的默认头像。
+// 说明:$name 是历史遗留参数(早期按姓名首字母生成彩色 SVG 头像),
+// 现在一律使用默认头像,参数仅保留以兼容既有调用点。
function getAvatarUrl($avatar_path, $name = '') {
if (storageExists($avatar_path)) {
return storageUrl($avatar_path);
}
- if (!empty($name)) {
- $letter = mb_substr($name, 0, 1, 'UTF-8');
- $colors = ['#e74c3c','#e67e22','#f1c40f','#2ecc71','#3498db','#9b59b6','#1abc9c','#e84393','#00b894','#0984e3'];
- $color = $colors[abs(crc32($name)) % count($colors)];
- return "data:image/svg+xml," . urlencode("");
- }
- return '/assets/default-avatar.png';
+ return defaultAvatarUrl();
}
// ===== 保存 Base64 图片 =====
diff --git a/persons.php b/persons.php
index b4dee44..a51b6cd 100644
--- a/persons.php
+++ b/persons.php
@@ -68,9 +68,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
// 保存新头像
$avatar_path = $old_avatar;
if (!empty($avatar_base64)) {
- // 删除旧头像
+ // 上传了新头像(优先级最高):先删旧文件,再存新的
deleteStoredFile($old_avatar);
$avatar_path = saveBase64Image($avatar_base64, AVATAR_DIR, 'avatar');
+ } elseif (isset($_POST['delete_avatar'])) {
+ // 用户点了「删除」:清空头像字段 → 页面回落到默认头像。
+ // (前端 removeAvatar() 会带上 delete_avatar=1;此前后端没有读这个标记,
+ // 导致点了「删除」保存后头像原样保留 —— 是个静默失效的按钮。)
+ deleteStoredFile($old_avatar);
+ $avatar_path = null;
}
$stmt = $db->prepare("UPDATE persons SET name = ?, color = ?, avatar = ? WHERE id = ?");
@@ -295,11 +301,8 @@ $preset_colors = [