['label' => '题图', 'ratio' => '16:9', 'w' => 1920, 'h' => 1080, * 'dir' => 'covers', 'hint' => '宽屏横幅,显示在详情页顶部'], */ declare(strict_types=1); namespace App; final class ImageSlot { // ⚠️ 应用层不再设「兆数」上限(用户原话:上传照片不要有大小限制)。 // 服务器 ini(upload_max_filesize / post_max_size,线上是 50M)是唯一的天花板; // 像素与内存的守卫在 Upload::image() 里按图动态算。 // 输出尺寸恒为槽位的 w×h(头像 512×512)——原图再大,落库的都是小 JPEG。 /** * 位置 → 比例与输出尺寸。 * * `dir` 是**存储前缀**:本地就是 uploads//,云端就是桶里的 //。 * `w`/`h` 是**输出**尺寸(不是要求上传者提供的尺寸)—— * 裁剪之后统一缩到这个大小,所以原图多大都不影响页面上的加载量。 * * ratio 写成字符串是为了让人一眼看懂,也方便直接拿去当 CSS 的 aspect-ratio。 */ public const SLOTS = [ 'avatar' => [ 'label' => '头像', 'ratio' => '1:1', 'w' => 512, 'h' => 512, 'dir' => 'avatars', 'hint' => '正方形。页面上显示成圆形或圆角方形,所以按 1:1 裁。', ], // v8 观影三槽位。海报是竖版的(豆瓣海报约 2:3)→ 裁剪; // 票根/现场照片是随手拍、比例五花八门 → ratio='free':**不裁剪**, // 等比缩到框内(w×h 是「最长边」而不是固定输出尺寸),服务端保证不变形。 'poster' => [ 'label' => '海报', 'ratio' => '2:3', 'w' => 600, 'h' => 900, 'dir' => 'posters', 'hint' => '竖版海报,按 2:3 裁。没裁就按整图居中取。', ], 'ticket' => [ 'label' => '票根', 'ratio' => 'free', 'w' => 1400, 'h' => 1400, 'dir' => 'tickets', 'hint' => '票根不裁剪,等比缩到最长边 1400。', ], 'photo' => [ 'label' => '照片', 'ratio' => 'free', 'w' => 1600, 'h' => 1600, 'dir' => 'photos', 'hint' => '现场照片不裁剪,等比缩到最长边 1600。', ], // 题图按 16:9 —— 宽屏横幅。需要时把它启用即可: // 'cover' => ['label' => '题图', 'ratio' => '16:9', 'w' => 1920, 'h' => 1080, // 'dir' => 'covers', 'hint' => '宽屏横幅,显示在详情页顶部。'], ]; public static function exists(string $key): bool { return isset(self::SLOTS[$key]); } /** @return array|null */ public static function get(string $key): ?array { return self::SLOTS[$key] ?? null; } /** 宽高比(宽 / 高)。'1:1' → 1.0,'16:9' → 1.777…,'free' → 0.0(不锁比例、不裁剪) */ public static function ratio(array $slot): float { if (($slot['ratio'] ?? '') === 'free') { return 0.0; } [$a, $b] = self::parseRatio((string)($slot['ratio'] ?? '1:1')); return $b > 0 ? $a / $b : 1.0; } /** 给前端用的 CSS aspect-ratio 写法:'1 / 1'、'16 / 9';free 槽位返回空串(前端不进裁剪器) */ public static function ratioCss(array $slot): string { if (($slot['ratio'] ?? '') === 'free') { return ''; } [$a, $b] = self::parseRatio((string)($slot['ratio'] ?? '1:1')); return $a . ' / ' . $b; } /** @return array{0:int,1:int} */ private static function parseRatio(string $ratio): array { // ⚠️ 分隔符别用 /:'[:/]' 里的斜杠会把模式提前截断("Unknown modifier ']'"), // 这个潜伏 bug 一直没炸是因为 ratio() 只在上传 POST 里被调到(302 正文没人扫)。 if (preg_match('#^\s*(\d+)\s*[:/]\s*(\d+)\s*$#', $ratio, $m)) { $a = max(1, (int)$m[1]); $b = max(1, (int)$m[2]); return [$a, $b]; } return [1, 1]; } }