This commit is contained in:
make
2026-01-10 19:59:40 +08:00
parent 8bb07eaf1a
commit f680c97559
13 changed files with 153 additions and 14 deletions

1
code/.gitignore vendored
View File

@@ -16,4 +16,5 @@ Thumbs.db
/public/initdata/video/url_family_bind.php /public/initdata/video/url_family_bind.php
public/static/css/compiled/* public/static/css/compiled/*
public/static/js/compiled/* public/static/js/compiled/*
public/static/favicon/generated/*
/storage/* /storage/*

View File

@@ -18,6 +18,10 @@ use think\facade\Cache;
*/ */
class SiteStyle class SiteStyle
{ {
// favicon SVG 母型目录
private static string $faviconBaseDir = 'public/static/favicon/svg_base/';
private static function useCache(): bool private static function useCache(): bool
{ {
return (bool) config('view.view_site_style_cache'); return (bool) config('view.view_site_style_cache');
@@ -268,16 +272,16 @@ class SiteStyle
} }
} }
// list_layout 只承载“形态库”item/shell/grid/layout/行为/itv // list_layout 只承载“形态库”item/shell/grid/layout/行为/itv
// title/title_text/更多文案 等“语义”交给 pages.slot/context 决定。 // title/title_text/更多文案 等“语义”交给 pages.slot/context 决定。
return [ $arrCfg = [
'meta' => [ 'meta' => [
'host' => $host, 'host' => $host,
'seed' => $seed, 'seed' => $seed,
'dom_prefix' => $domPrefix, 'dom_prefix' => $domPrefix,
'static_hash' => $staticHash, 'static_hash' => $staticHash,
'version' => self::CFG_VERSION, 'version' => self::CFG_VERSION,
], ],
'global' => [ 'global' => [
@@ -289,6 +293,8 @@ class SiteStyle
], ],
], ],
'theme' => [],
'pages' => [ 'pages' => [
'home' => $homePage, 'home' => $homePage,
'category_index' => $categoryIndexPage, 'category_index' => $categoryIndexPage,
@@ -347,6 +353,17 @@ class SiteStyle
'seo' => self::buildSeoCfg($seed), 'seo' => self::buildSeoCfg($seed),
]; ];
// 主题色(运行时算,不冻结具体颜色)
$theme = self::buildTheme(
$seed,
$arrCfg['global']['theme']['mode'],
$arrCfg['global']['theme']['variant']
);
$arrCfg['theme'] = $theme;
$arrCfg['favicon'] = self::buildFavicon($arrCfg);
return $arrCfg;
} }
private static function getUrlFamilyPool(): array private static function getUrlFamilyPool(): array
@@ -445,11 +462,7 @@ class SiteStyle
$staticHash = $cfg['meta']['static_hash']; $staticHash = $cfg['meta']['static_hash'];
// 主题色(运行时算,不冻结具体颜色) // 主题色(运行时算,不冻结具体颜色)
$theme = self::buildTheme( $theme = $cfg['theme'];
$intSeed,
$cfg['global']['theme']['mode'],
$cfg['global']['theme']['variant']
);
// 模板路径(你原来 include 用的方式完全保留) // 模板路径(你原来 include 用的方式完全保留)
$tpl = $cfg['components']['templates']; $tpl = $cfg['components']['templates'];
@@ -483,6 +496,8 @@ class SiteStyle
'dom_prefix' => $domPrefix, 'dom_prefix' => $domPrefix,
'static_hash' => $staticHash, 'static_hash' => $staticHash,
'favicon' => $cfg['favicon'],
'lazy_img' => self::pickLazyImg([ 'lazy_img' => self::pickLazyImg([
'host' => $strHost, 'host' => $strHost,
'seed' => $intSeed, 'seed' => $intSeed,
@@ -795,13 +810,13 @@ class SiteStyle
'seo' => '', 'seo' => '',
]; ];
} }
$pool = require $file; $pool = require $file;
$primaryPool = $pool['primary'] ?? []; $primaryPool = $pool['primary'] ?? [];
$secondaryPool = $pool['secondary'] ?? []; $secondaryPool = $pool['secondary'] ?? [];
$seoPool = $pool['seo'] ?? []; $seoPool = $pool['seo'] ?? [];
// 池兜底(防止被误删 / 配置异常) // 池兜底(防止被误删 / 配置异常)
if (!$primaryPool || !$secondaryPool || !$seoPool) { if (!$primaryPool || !$secondaryPool || !$seoPool) {
return [ return [
@@ -810,21 +825,21 @@ class SiteStyle
'seo' => '', 'seo' => '',
]; ];
} }
// 域名级 + 榜单级稳定 // 域名级 + 榜单级稳定
$hP = abs(crc32($seed . '|rkpri|' . $sortType)); $hP = abs(crc32($seed . '|rkpri|' . $sortType));
$hS = abs(crc32($seed . '|rksec|' . $sortType)); $hS = abs(crc32($seed . '|rksec|' . $sortType));
$hE = abs(crc32($seed . '|rkseo|' . $sortType)); $hE = abs(crc32($seed . '|rkseo|' . $sortType));
$primaryTpl = $primaryPool[$hP % count($primaryPool)]; $primaryTpl = $primaryPool[$hP % count($primaryPool)];
$secondaryTpl = $secondaryPool[$hS % count($secondaryPool)]; $secondaryTpl = $secondaryPool[$hS % count($secondaryPool)];
$seoTpl = $seoPool[$hE % count($seoPool)]; $seoTpl = $seoPool[$hE % count($seoPool)];
// 统一占位符替换rank_home 统一用 {name} // 统一占位符替换rank_home 统一用 {name}
$replace = [ $replace = [
'{name}' => $titleName, '{name}' => $titleName,
]; ];
return [ return [
'primary' => strtr($primaryTpl, $replace), 'primary' => strtr($primaryTpl, $replace),
'secondary' => strtr($secondaryTpl, $replace), 'secondary' => strtr($secondaryTpl, $replace),
@@ -2597,4 +2612,78 @@ class SiteStyle
return $memo[$key] = $data; return $memo[$key] = $data;
return $memo[$relFile] = (is_array($data) ? $data : []); return $memo[$relFile] = (is_array($data) ? $data : []);
} }
/**
* 生成并冻结 favicon与 theme_color / dom_prefix / layout 联动)
* 只在 SiteStyle 构建阶段调用
*/
private static function buildFavicon(array $TpStyle): string
{
$domain = $_SERVER['HTTP_HOST'] ?? 'cli';
$safe = preg_replace('/[^a-z0-9\.\-]/i', '_', $domain);
// ===== 下面开始“只执行一次”的生成逻辑 =====
$seed = (int)($TpStyle['meta']['seed'] ?? crc32($domain));
$bg = (string)($TpStyle['theme']['theme_color'] ?? '#263238');
$fg = '#ffffff';
$strSvgRoute = '/static/favicon/generated/' . $seed . '/';
$strSvgGenDir = root_path() . 'public/' . $strSvgRoute;
$strTmpSvgDir = $strSvgRoute . 'favicon.svg';
$svgFile = $strSvgGenDir . 'favicon.svg';
$icoFile = $strSvgGenDir . 'favicon.ico';
if (!is_dir($strSvgGenDir)) {
@mkdir($strSvgGenDir, 0755, true);
}
// 已生成过,直接复用(冻结)
if (is_file($icoFile) && is_file($svgFile)) {
return $strTmpSvgDir;
}
$svgBasePath = root_path() . self::$faviconBaseDir;
$bases = glob($svgBasePath . '*.svg');
if (!$bases) {
// 兜底,不影响上线
return '/favicon.ico';
}
// 用 dom_prefix + layout 决定母型(稳定、可回放)
$layout = (string)($TpStyle['global']['theme']['layout'] ?? 'A');
$dom = (string)($TpStyle['meta']['dom_prefix'] ?? '');
$idx = abs(crc32($layout . '|' . $dom . '|' . $seed)) % count($bases);
$baseSvg = $bases[$idx];
$svg = file_get_contents($baseSvg);
if ($svg === false) {
return '/favicon.ico';
}
// 注入主题色
$svg = str_replace(['VAR_BG', 'VAR_FG'], [$bg, $fg], $svg);
// 持久化 svg便于排查、也便于后续升级
@file_put_contents($svgFile, $svg);
// 转 ico16 / 32
$bin = 'magick';
if (!shell_exec('which magick')) {
$bin = 'convert';
}
$cmd = sprintf(
'%s %s -background none -define icon:auto-resize=16,32 %s',
$bin,
escapeshellarg($svgFile),
escapeshellarg($icoFile)
);
@exec($cmd);
return $strTmpSvgDir;
}
} }

View File

@@ -8,6 +8,9 @@
<meta name="keywords" content='{block name="keywords"}{/block}'> <meta name="keywords" content='{block name="keywords"}{/block}'>
<meta name="description" content='{block name="description"}{/block}'> <meta name="description" content='{block name="description"}{/block}'>
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
<link rel="icon" href="{$TpStyle.favicon}" type="image/svg+xml">
{// 动态主题颜色 } {// 动态主题颜色 }
<style> <style>
:root{ :root{

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<rect width="16" height="16" fill="VAR_BG"/>
<path d="M5 4h4c2 0 3 1 3 2.5S11 9 9 9H7v3H5z" fill="VAR_FG"/>
</svg>

After

Width:  |  Height:  |  Size: 179 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<rect width="16" height="16" fill="VAR_BG"/>
<path d="M9 1L3 9h4l-1 6 7-9H9z" fill="VAR_FG"/>
</svg>

After

Width:  |  Height:  |  Size: 165 B

View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<rect width="16" height="16" fill="VAR_BG"/>
<circle cx="8" cy="8" r="5" fill="VAR_FG"/>
<circle cx="6" cy="6" r="1" fill="VAR_BG"/>
<circle cx="10" cy="6" r="1" fill="VAR_BG"/>
<circle cx="6" cy="10" r="1" fill="VAR_BG"/>
<circle cx="10" cy="10" r="1" fill="VAR_BG"/>
</svg>

After

Width:  |  Height:  |  Size: 348 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<rect width="16" height="16" fill="VAR_BG"/>
<path d="M8 3l5 10H3z" fill="VAR_FG"/>
</svg>

After

Width:  |  Height:  |  Size: 155 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<rect width="16" height="16" fill="VAR_BG"/>
<polygon points="6,4 12,8 6,12" fill="VAR_FG"/>
</svg>

After

Width:  |  Height:  |  Size: 164 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<rect width="16" height="16" fill="VAR_BG"/>
<rect x="4" y="3" width="3" height="10" fill="VAR_FG"/>
<rect x="9" y="3" width="3" height="10" fill="VAR_FG"/>
</svg>

After

Width:  |  Height:  |  Size: 230 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<rect width="16" height="16" fill="VAR_BG"/>
<path d="M3 4h10v6H7l-2 2v-2H3z" fill="VAR_FG"/>
</svg>

After

Width:  |  Height:  |  Size: 165 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<rect width="16" height="16" fill="VAR_BG"/>
<ellipse cx="8" cy="8" rx="5" ry="3" fill="VAR_FG"/>
<circle cx="8" cy="8" r="1.5" fill="VAR_BG"/>
</svg>

After

Width:  |  Height:  |  Size: 217 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<rect width="16" height="16" fill="VAR_BG"/>
<rect x="4" y="4" width="8" height="8" fill="VAR_FG"/>
</svg>

After

Width:  |  Height:  |  Size: 171 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<rect width="16" height="16" fill="VAR_BG"/>
<path d="M2 9c2-3 4 3 6 0s4 3 6 0" stroke="VAR_FG" stroke-width="2" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 198 B