]+src=["\']([^"\']+)["\']/i', $content, $m)) { $thumb = $m[1]; } $fileDate = str_replace('.txt', '', $file); $date = (strlen($fileDate) >= 14 && is_numeric($fileDate)) ? date('Y-m-d', strtotime($fileDate)) : date('Y-m-d', filemtime($filePath)); $allPosts[] = [ 'cat' => $cat, 'file' => $file, 'title' => $title, 'views' => $views, 'thumb' => $thumb, 'date' => $date ]; } } } // 1. 熱門文章(點閱 views 由大到小排序,取前 10 篇) $popularPosts = $allPosts; usort($popularPosts, function($a, $b) { if ($b['views'] === $a['views']) return strcmp($b['file'], $a['file']); return $b['views'] - $a['views']; }); $popularTop10 = array_slice($popularPosts, 0, 10); // 2. 關注文章(最冷門 / 點閱 views 由小到大排序,取前 10 篇) $focusPosts = $allPosts; usort($focusPosts, function($a, $b) { if ($a['views'] === $b['views']) return strcmp($b['file'], $a['file']); return $a['views'] - $b['views']; }); $focusTop10 = array_slice($focusPosts, 0, 10); $showcaseData = [ 'generated_at' => date('Y-m-d H:i:s'), 'popular' => $popularTop10, 'focus' => $focusTop10 ]; @file_put_contents($cacheFile, json_encode($showcaseData, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)); } $popularList = $showcaseData['popular'] ?? []; $focusList = $showcaseData['focus'] ?? []; // 預設縮圖 (當文章內沒有任何圖片時使用) $defaultThumb = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='200' viewBox='0 0 300 200'%3E%3Crect width='300' height='200' fill='%231e1e1e'/%3E%3Cpath d='M80 140 L120 90 L160 130 L190 85 L230 140 Z' fill='%23333'/%3E%3Ccircle cx='120' cy='65' r='14' fill='%23d4af37' opacity='0.7'/%3E%3Ctext x='50%25' y='88%25' fill='%23777' font-size='13' text-anchor='middle' font-family='sans-serif'%3E正妹不在家%3C/text%3E%3C/svg%3E"; // ================= 3. 推薦相簿:讀取 dirlist.txt 並隨機挑選縮圖 ================= /** * 解析 dirlist.txt,取得所有相簿目錄的「相對路徑」清單。 * dirlist.txt 每行格式範例: * X:\taiwan\台中20號倉庫藝術村\文化義工 * 會被轉換為相對路徑: * taiwan/台中20號倉庫藝術村/文化義工 */ /** * 把一行文字轉成 UTF-8。 * * 真正的問題根源:dirlist.txt 是在「Windows 繁體中文版 cmd.exe」下產生的, * cmd.exe 預設輸出編碼是 Big5(碼頁 950),而 Synology / Linux 上實際的 * 檔案系統與目錄名稱是 UTF-8。同一組中文字,Big5 與 UTF-8 的位元組完全不同, * 所以只要路徑中含中文,逐位元組比對 (is_dir/glob) 必定失敗;只有純英文/數字 * 路徑(如 Disk3、other、200005)因為 ASCII 在兩種編碼下位元組相同才會成功, * 這正好對應你觀察到「只有純英文路徑正常」的現象。 * * 這裡先偵測該行是否已經是合法 UTF-8(例如日後改用 PowerShell/UTF-8 匯出 * 就不需要再轉碼),若不是,才嘗試以 Big5 轉成 UTF-8。 */ function convertLineToUtf8($str) { if ($str === '') return $str; // 已經是合法 UTF-8,直接使用,不做任何轉換 if (function_exists('mb_check_encoding') && mb_check_encoding($str, 'UTF-8')) { return $str; } // 優先使用 iconv 轉碼(PHP 內建擴充套件,Synology 上通常已啟用) if (function_exists('iconv')) { $out = @iconv('BIG5', 'UTF-8//IGNORE', $str); if ($out !== false && $out !== '') { return $out; } } // iconv 不可用時,退而使用 mbstring 轉碼 if (function_exists('mb_convert_encoding')) { $out = @mb_convert_encoding($str, 'UTF-8', 'BIG5'); if ($out !== false && $out !== '') { return $out; } } // 兩種轉碼方式都無法使用,只好原樣返回(極少數情況) return $str; } function loadAlbumDirList($dirListFile) { $dirs = []; if (!is_file($dirListFile)) { return $dirs; } // dirlist.txt 若是在 Windows 繁體中文 cmd.exe 下用 "dir /s /b > dirlist.txt" // 之類方式產生,整個檔案很可能是 Big5 編碼、行尾為 CRLF。 // 這裡直接讀取原始位元組再逐行處理,而不是用 file() 依賴系統 locale 判斷。 $raw = file_get_contents($dirListFile); if ($raw === false || $raw === '') { return $dirs; } // 去除可能存在的 UTF-8 BOM if (substr($raw, 0, 3) === "\xEF\xBB\xBF") { $raw = substr($raw, 3); } // 統一換行符號後切成一行一行 $raw = str_replace("\r\n", "\n", $raw); $raw = str_replace("\r", "\n", $raw); $lines = explode("\n", $raw); foreach ($lines as $line) { $line = trim($line); if ($line === '') continue; // 先轉成 UTF-8,確保後續與磁碟上的實際(UTF-8)目錄名稱比對一致 $line = convertLineToUtf8($line); // 去除開頭的磁碟機代號,如 "X:\" 或 "X:/" $rel = preg_replace('#^[A-Za-z]:[\\\\/]#', '', $line); // 反斜線一律轉為正斜線 $rel = str_replace('\\', '/', $rel); // 去除頭尾多餘的斜線與空白 $rel = trim($rel, "/ \t\n\r\0\x0B"); if ($rel !== '') { $dirs[] = $rel; } } // 去除重複目錄 return array_values(array_unique($dirs)); } /** * 將 dirlist.txt 內的相對路徑,逐層對應到實際檔案系統上的目錄。 * * 修正說明:先前版本只用一次 is_dir($docRoot . '/' . $relDir) 直接比對整條路徑, * 但 dirlist.txt 是從其他裝置(如透過網路磁碟機 X: 匯出)取得的路徑字串, * 大小寫、全形/半形、或路徑分隔習慣可能與 Synology (Linux,檔案系統區分大小寫) * 上實際目錄不完全一致。只要路徑中有任何一層對不上,is_dir() 就會直接判定失敗, * 而失敗的機率通常和列表順序無關,但實務上常造成「只有列表前面少數幾筆能命中」 * 的現象。這裡改成逐層比對:先嘗試完全相符,若對不上,該層改用「忽略大小寫」 * 掃描目前目錄底下的項目來找出真正對應的資料夾,大幅提高比對成功率。 * * @return string|null 找到則回傳實際存在的完整檔案系統路徑,找不到則回傳 null */ function resolveActualDir($docRoot, $relDir) { $current = rtrim($docRoot, '/'); $segments = explode('/', $relDir); foreach ($segments as $seg) { if ($seg === '') continue; $candidate = $current . '/' . $seg; if (is_dir($candidate)) { $current = $candidate; continue; } // 完全相符失敗,改用忽略大小寫方式在目前層級尋找相符的子目錄 $matched = null; $items = @scandir($current); if ($items !== false) { foreach ($items as $item) { if ($item === '.' || $item === '..') continue; if (strcasecmp($item, $seg) === 0 && is_dir($current . '/' . $item)) { $matched = $item; break; } } } if ($matched === null) { return null; // 這一層完全找不到相符目錄,判定此路徑無效 } $current = $current . '/' . $matched; } return $current; } /** * 從指定的相簿相對路徑清單中,隨機挑選最多 $count 組「有 .jpg 圖片」的相簿。 * * 挑選邏輯(每次都從「目前尚未嘗試過」的候選池中隨機抽一個,抽過就從池中移除, * 確保每一輪抽選涵蓋整份清單、不會偏向列表前段): * 1. 從候選池中隨機挑一個目錄,並將它從池中移除(不重複嘗試)。 * 2. 判斷該目錄底下是否有 .jpg 圖片: * - 目錄不存在(含大小寫容錯後仍找不到)或裡面沒有任何 .jpg -> 略過, * 回到步驟 1 繼續隨機挑下一個目錄。 * - 有 .jpg -> 從中隨機挑一張作為縮圖,該相簿入選。 * 3. 重複以上動作,直到湊滿 $count 筆「精選相簿」,或候選池已經抽完為止。 * * @param array $albumDirs 相簿相對路徑清單 * @param string $docRoot 網站根目錄(用來組出實際檔案系統路徑) * @param int $count 希望取得的相簿數量,上限固定為 10 * @return array 每筆為 ['title'=>..., 'dir'=>相對目錄, 'img'=>相對圖片路徑] */ function pickRandomAlbums($albumDirs, $docRoot, $count = 12) { $count = max(0, min(12, (int)$count)); // 精選相簿數量最多 12 個 $picked = []; if ($count === 0 || empty($albumDirs)) { return $picked; } // 候選池:存放尚未嘗試過的 $albumDirs 索引 $pool = array_keys($albumDirs); while (!empty($pool) && count($picked) < $count) { // 從「目前剩餘」的候選池中均勻隨機抽一筆 $poolKey = array_rand($pool); $idx = $pool[$poolKey]; // 立刻從池中移除,避免重複嘗試、也保證迴圈一定會結束 unset($pool[$poolKey]); $pool = array_values($pool); $relDir = $albumDirs[$idx]; $actualDir = resolveActualDir($docRoot, $relDir); if ($actualDir === null) { continue; // 目錄不存在,略過,繼續隨機挑下一個 } // 掃描該目錄下的 .jpg 檔案(不分大小寫),不遞迴子目錄 $jpgFiles = glob($actualDir . '/*.[jJ][pP][gG]'); if ($jpgFiles === false || empty($jpgFiles)) { continue; // 沒有 jpg 圖片,略過並繼續隨機挑下一個目錄 } // 隨機挑一張圖片作為縮圖 $chosenFile = $jpgFiles[array_rand($jpgFiles)]; // 轉成相對於網站根目錄的圖片路徑 $relImg = ltrim(substr($chosenFile, strlen(rtrim($docRoot, '/'))), '/'); // 相簿標題:相簿目錄「完整路徑」去掉所有 "/",且不顯示 "taiwan" 這層根目錄名稱 // (過長由前端 CSS 裁切顯示) $titleSegments = array_filter(explode('/', $relDir), function ($seg) { return strcasecmp($seg, 'taiwan') !== 0; }); $title = implode('', $titleSegments); $picked[] = [ 'title' => $title, 'dir' => $relDir, 'img' => $relImg, ]; } return $picked; } $dirListFile = __DIR__ . '/dirlist.txt'; $albumRecommendCount = 12; // 首頁「推薦相簿」顯示幾組,最多 12 個 $albumDirs = loadAlbumDirList($dirListFile); $recommendAlbums = pickRandomAlbums($albumDirs, $docRoot, $albumRecommendCount); // 相簿瀏覽連結格式,沿用站內既有慣例:photo.php?dir=./相對路徑/ function albumLink($relDir) { $encodedSegments = array_map('rawurlencode', explode('/', $relDir)); return 'photo.php?dir=./' . implode('/', $encodedSegments) . '/'; } // 相簿縮圖 路徑(相對路徑直接輸出,讓瀏覽器自行處理 UTF-8 檔名) function albumImgSrc($relImg) { $encodedSegments = array_map('rawurlencode', explode('/', $relImg)); return implode('/', $encodedSegments); } ?> 正妹不在家 | 台灣紀實攝影與20號倉庫歷史影像典藏入口

正妹不在家

Photography & Archive Project

台灣早期紀實攝影與台中20號倉庫歷史影像典藏

✨ 關注文章 (潛力精選)

每週精選推薦 ✦ 最值得探索的好文
尚無文章數據
$p): $postImg = !empty($p['thumb']) ? $p['thumb'] : $defaultThumb; $postUrl = $blogBaseUrl . '?cat=' . urlencode($p['cat']) . '&post=' . urlencode($p['file']); ?>
<?= htmlspecialchars($p['title']) ?> # 👁️

每週統計 ✦ 全站點閱最高 Top 10
尚無文章數據
$p): $postImg = !empty($p['thumb']) ? $p['thumb'] : $defaultThumb; $postUrl = $blogBaseUrl . '?cat=' . urlencode($p['cat']) . '&post=' . urlencode($p['file']); ?>
<?= htmlspecialchars($p['title']) ?> # 🔥

📷 推薦相簿 (隨機精選)

每次造訪隨機呈現 ✦ 珍藏影像目錄
尚無可顯示的相簿(請確認 dirlist.txt 是否存在,或目錄中是否有 .jpg 圖片)
$a): ?>
<?= htmlspecialchars($a['title']) ?> #