gpttemp
This commit is contained in:
121
code/app/common/helper/CssBuilder.php
Normal file
121
code/app/common/helper/CssBuilder.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
namespace app\common\helper;
|
||||
|
||||
class CssBuilder
|
||||
{
|
||||
/**
|
||||
* 构建当前域名的 CSS(ThinkPHP 最终稳定版本)
|
||||
* - 自动合并模块 CSS
|
||||
* - 自动加入 global_base.css(全局布局)
|
||||
* - 自动替换 __PFX__
|
||||
* - 自动压缩空白
|
||||
* - 完全兼容 TP 的 public/static/
|
||||
*/
|
||||
public static function build(array $cssFiles, string $domPrefix, string $staticHash): string
|
||||
{
|
||||
// Public 目录路径
|
||||
$publicPath = root_path() . 'public' . DIRECTORY_SEPARATOR;
|
||||
|
||||
// 模块 CSS 存放目录
|
||||
$baseDir = $publicPath . "static/css/modules/";
|
||||
|
||||
// 构建后的合并文件目录
|
||||
$targetDir = $publicPath . "static/css/compiled/";
|
||||
|
||||
// 自动创建 compiled 目录
|
||||
if (!is_dir($targetDir)) {
|
||||
@mkdir($targetDir, 0755, true);
|
||||
}
|
||||
|
||||
// 输出文件名(作为域名专属缓存)
|
||||
$targetFile = $targetDir . "dom_{$staticHash}.css";
|
||||
|
||||
// 如果已经生成过,直接返回
|
||||
if (file_exists($targetFile)) {
|
||||
return "/static/css/compiled/dom_{$staticHash}.css";
|
||||
}
|
||||
|
||||
$listCss = [
|
||||
"list/list_base.css","list/list_cols.css","list/list_rows.css",
|
||||
"title/title_A.css","list/title/title_B.css","list/title/title_C.css","list/title/title_D.css","list/title/title_E.css",
|
||||
"list/shell/shell_A.css","list/shell/shell_B.css","list/shell/shell_C.css","list/shell/shell_D.css",
|
||||
"list/item/_item_media.css","list/item/_item_poster.css","list/item/_item_rank.css","list/item/_item_text.css",
|
||||
"list/item/_item_base.css","list/item/_item_title_clamp.css",
|
||||
"list/item/item_01.css","list/item/item_02.css","list/item/item_03.css","list/item/item_04.css","list/item/item_05.css",
|
||||
"list/behavior/behavior.css",
|
||||
"pager/pager_base.css",
|
||||
"seowords/seowords.css",
|
||||
"breadcrumb/breadcrumb.css",
|
||||
|
||||
// "rank/rank_home.css",
|
||||
];
|
||||
$cssFiles[] = 'detail_main/detail_main.base.css';
|
||||
$cssFiles[] = 'detail_main/title.css';
|
||||
$cssFiles[] = 'detail_main/cover.css';
|
||||
$cssFiles[] = 'detail_main/meta.css';
|
||||
$cssFiles[] = 'detail_main/desc.css';
|
||||
$cssFiles[] = 'detail_main/action.css';
|
||||
|
||||
$cssFiles[] = 'player/player_dplayer.css';
|
||||
|
||||
|
||||
// $cssFiles[] = 'detail_main/layout/layout_C.css';
|
||||
// $cssFiles[] = 'detail_main/layout/layout_B.css';
|
||||
// $cssFiles[] = 'detail_main/layout/layout_A.css';
|
||||
|
||||
// $cssFiles[] = 'playline/layout/layout_C.css';
|
||||
// $cssFiles[] = 'playline/layout/layout_B.css';
|
||||
// $cssFiles[] = 'playline/layout/layout_A.css';
|
||||
|
||||
// $cssFiles[] = 'breadcrumb/layout/layout_C.css';
|
||||
// $cssFiles[] = 'breadcrumb/layout/layout_B.css';
|
||||
// $cssFiles[] = 'breadcrumb/layout/layout_A.css';
|
||||
|
||||
|
||||
$cssFiles = array_merge($cssFiles, $listCss);
|
||||
|
||||
|
||||
// ----------------------------------------
|
||||
// ★ 关键:强制加入全局基础 CSS
|
||||
// ----------------------------------------
|
||||
$finalCssFiles = array_merge(
|
||||
['global_base.css'], // <<< 必须放第一位
|
||||
$cssFiles
|
||||
);
|
||||
$finalCssFiles = array_unique($finalCssFiles);
|
||||
|
||||
// ----------------------------------------
|
||||
// 合并 CSS 文件内容
|
||||
// ----------------------------------------
|
||||
$allCss = "";
|
||||
|
||||
foreach ($finalCssFiles as $file) {
|
||||
|
||||
$path = $baseDir . $file;
|
||||
|
||||
if (!is_file($path)) {
|
||||
// 你可以取消注释来调试
|
||||
// echo "<!-- missing css: {$path} -->";
|
||||
continue;
|
||||
}
|
||||
|
||||
$css = file_get_contents($path);
|
||||
|
||||
// 类名前缀替换
|
||||
$css = str_replace('__PFX__', $domPrefix, $css);
|
||||
|
||||
// 简单压缩(减少 fingerprint 重复率)
|
||||
$css = preg_replace('/\s+/', ' ', $css);
|
||||
|
||||
// 模块标记
|
||||
// $allCss .= "/* {$file} */" . $css;
|
||||
$allCss .= $css;
|
||||
}
|
||||
|
||||
// 写入合并后的 CSS
|
||||
file_put_contents($targetFile, $allCss);
|
||||
|
||||
// 返回前端可访问路径
|
||||
return "/static/css/compiled/dom_{$staticHash}.css";
|
||||
}
|
||||
}
|
||||
115
code/app/common/helper/SeoWordsHelper.php
Normal file
115
code/app/common/helper/SeoWordsHelper.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\helper;
|
||||
|
||||
class SeoWordsHelper
|
||||
{
|
||||
/**
|
||||
* 根据 $intRewrite 处理 SeoWords
|
||||
*/
|
||||
public static function build(
|
||||
array $words,
|
||||
int $intRewrite,
|
||||
array $video = []
|
||||
): array {
|
||||
if (empty($words)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$arrSeoWords = [];
|
||||
switch ($intRewrite) {
|
||||
|
||||
// 0:原序全部
|
||||
case 0:
|
||||
$arrSeoWords = $words;
|
||||
|
||||
// 1:前 6
|
||||
case 1:
|
||||
$arrSeoWords = array_slice($words, 0, 6);
|
||||
|
||||
// 2:随机 8
|
||||
case 2:
|
||||
shuffle($words);
|
||||
$arrSeoWords = array_slice($words, 0, 8);
|
||||
|
||||
// 3:插年份
|
||||
case 3:
|
||||
$arrSeoWords = self::appendYear($words, $video);
|
||||
|
||||
// 4:插“在线观看 / 免费 / 高清”
|
||||
case 4:
|
||||
$arrSeoWords = self::appendSuffix($words);
|
||||
|
||||
// 5:重排 + 去重
|
||||
case 5:
|
||||
shuffle($words);
|
||||
$arrSeoWords = array_values(array_unique($words));
|
||||
|
||||
// 兜底
|
||||
default:
|
||||
$arrSeoWords = array_slice($words, 0, 6);
|
||||
}
|
||||
|
||||
$arrNewsSeoWords = [];
|
||||
|
||||
foreach ($arrSeoWords as $w) {
|
||||
|
||||
$arrNewsSeoWords[] = ['text' => $w, 'url' => 'javascript:void(0)'];
|
||||
|
||||
}
|
||||
|
||||
return $arrNewsSeoWords;
|
||||
}
|
||||
|
||||
/* ================== 子策略 ================== */
|
||||
|
||||
private static function appendYear(array $words, array $video): array
|
||||
{
|
||||
$year = $video['v_year'] ?? date('Y');
|
||||
$out = [];
|
||||
|
||||
foreach ($words as $w) {
|
||||
// 防止重复插
|
||||
if (strpos($w, (string)$year) === false) {
|
||||
$out[] = $year . $w;
|
||||
} else {
|
||||
$out[] = $w;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
private static function appendSuffix(array $words): array
|
||||
{
|
||||
$suffixGroups = [
|
||||
'watch' => ['在线观看', '在线播放', '在线免费看'],
|
||||
'free' => ['免费观看', '高清免费', '全集免费观看'],
|
||||
'hd' => ['高清版', '蓝光版', '1080P', '4K版'],
|
||||
'scene' => ['手机在线观看', '手机免费观看', '电脑在线观看'],
|
||||
'new' => ['最新资源', '最新上线'],
|
||||
];
|
||||
|
||||
$groupKeys = array_keys($suffixGroups);
|
||||
$out = [];
|
||||
|
||||
foreach ($words as $w) {
|
||||
// 50% 概率加后缀
|
||||
if ((crc32($w) & 1) === 0) {
|
||||
$out[] = $w;
|
||||
continue;
|
||||
}
|
||||
|
||||
$group = $groupKeys[crc32($w) % count($groupKeys)];
|
||||
$suffixList = $suffixGroups[$group];
|
||||
$suffix = $suffixList[crc32($w) % count($suffixList)];
|
||||
|
||||
if (!str_contains($w, $suffix)) {
|
||||
$out[] = $w . $suffix;
|
||||
} else {
|
||||
$out[] = $w;
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
907
code/app/common/helper/SiteStyle-back.php
Normal file
907
code/app/common/helper/SiteStyle-back.php
Normal file
@@ -0,0 +1,907 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\helper;
|
||||
|
||||
class SiteStyle
|
||||
{
|
||||
/* ======================================================
|
||||
* 公共入口
|
||||
* ====================================================== */
|
||||
public static function getConfig($domainRow = null): array
|
||||
{
|
||||
$host = self::resolveHost();
|
||||
$seed = self::resolveSeed($host);
|
||||
|
||||
// 1) 读取或生成冻结 cfg(必须持久化)
|
||||
$cfg = self::loadOrGenerateCfg($domainRow, $host, $seed);
|
||||
|
||||
// 2) 运行时 payload(TpStyle 用这个)
|
||||
return self::buildRuntimePayload($cfg, $host, $seed, $domainRow);
|
||||
}
|
||||
|
||||
/* ======================================================
|
||||
* Host / Seed
|
||||
* ====================================================== */
|
||||
private static function resolveHost(): string
|
||||
{
|
||||
$host = $_SERVER['HTTP_HOST'] ?? 'default.com';
|
||||
$host = strtolower(preg_replace('/^www\./i', '', $host));
|
||||
$host = explode(':', $host)[0];
|
||||
return $host ?: 'default.com';
|
||||
}
|
||||
|
||||
private static function resolveSeed(string $host): int
|
||||
{
|
||||
return crc32($host);
|
||||
}
|
||||
|
||||
/* ======================================================
|
||||
* 读取 / 生成 / 补齐 / 持久化 cfg(冻结层)
|
||||
* ====================================================== */
|
||||
private static function loadOrGenerateCfg($domainRow, string $host, int $seed): array
|
||||
{
|
||||
$cfg = self::readDbConfig($domainRow)
|
||||
?? self::readLocalJson($host)
|
||||
?? self::generateFrozenCfg($host, $seed);
|
||||
|
||||
// 补齐旧 cfg(版本升级用)
|
||||
$patched = false;
|
||||
[$cfg, $patched] = self::patchCfgIfNeeded($cfg, $host, $seed);
|
||||
|
||||
// 不管来源是 DB/JSON,只要补丁发生就必须写回(保证可控)
|
||||
if ($patched) {
|
||||
self::persistCfg($domainRow, $host, $cfg);
|
||||
}
|
||||
|
||||
// 如果是新生成的,也要写回
|
||||
if (!isset($cfg['_persisted'])) {
|
||||
$cfg['_persisted'] = 1;
|
||||
self::persistCfg($domainRow, $host, $cfg);
|
||||
}
|
||||
|
||||
return $cfg;
|
||||
}
|
||||
|
||||
private static function persistCfg($domainRow, string $host, array $cfg): void
|
||||
{
|
||||
// 去掉运行时标记(避免污染)
|
||||
$store = $cfg;
|
||||
unset($store['_persisted']);
|
||||
|
||||
self::writeDbConfig($domainRow, $store);
|
||||
self::writeLocalJson($host, $store);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新站:生成冻结 cfg(四层结构)
|
||||
*/
|
||||
private static function generateFrozenCfg(string $host, int $seed): array
|
||||
{
|
||||
// dom_prefix / hash 必须跟 host 强绑定(不要用 seed)
|
||||
$domPrefix = substr(md5($host . '_dom'), 0, 6);
|
||||
$staticHash = substr(md5($host . '_v2025'), 0, 10);
|
||||
|
||||
// 模板编号(沿用你旧的 1-5)
|
||||
$idx = fn($shift) => (($seed >> $shift) % 5) + 1;
|
||||
|
||||
// theme 冻结:mode/variant(调色板运行时算,也可冻结存)
|
||||
$themeMode = ['A', 'B', 'C', 'D'][$seed % 4];
|
||||
$themeVariant = ($seed % 4) + 1;
|
||||
|
||||
// grid 冻结(所有页面通用)
|
||||
$grid = self::buildGridLayout($seed);
|
||||
|
||||
// pages 冻结:home/category
|
||||
$homePage = self::buildHomePageCfg($seed);
|
||||
$categoryPage = self::buildCategoryPageCfg($seed);
|
||||
|
||||
// list_layout 冻结:为“会被渲染的模块”提前生成(后续加模块只要补丁即可)
|
||||
// 说明:这里生成的是“模块级默认布局”,页面渲染时按 module 取用即可
|
||||
$listLayout = self::buildDefaultListLayouts($seed, $homePage, $categoryPage);
|
||||
|
||||
return [
|
||||
'meta' => [
|
||||
'host' => $host,
|
||||
'seed' => $seed,
|
||||
'dom_prefix' => $domPrefix,
|
||||
'static_hash' => $staticHash,
|
||||
'version' => 4, // 你现在的 cfg 版本号(以后升级用)
|
||||
],
|
||||
|
||||
'global' => [
|
||||
'page_max_width_pc' => self::$WIDTH_POOL[$seed % count(self::$WIDTH_POOL)],
|
||||
'grid' => $grid,
|
||||
'theme' => [
|
||||
'mode' => $themeMode,
|
||||
'variant' => $themeVariant,
|
||||
],
|
||||
],
|
||||
|
||||
'pages' => [
|
||||
'home' => $homePage,
|
||||
'category' => $categoryPage,
|
||||
],
|
||||
|
||||
'components' => [
|
||||
'templates' => [
|
||||
'head_tpl' => $idx(0),
|
||||
'foot_tpl' => $idx(1),
|
||||
'banner_tpl' => $idx(2),
|
||||
'list_tpl' => $idx(3),
|
||||
'detail_tpl' => $idx(9),
|
||||
'play_tpl' => $idx(10),
|
||||
|
||||
// 你旧系统里还有 recommend/trending/newest/ranking/category 等独立 tpl,可继续保留
|
||||
'recommend_tpl' => $idx(4),
|
||||
'trending_tpl' => $idx(5),
|
||||
'newest_tpl' => $idx(6),
|
||||
'ranking_tpl' => $idx(7),
|
||||
'category_tpl' => $idx(8),
|
||||
],
|
||||
|
||||
'list' => [
|
||||
'item_variants' => [
|
||||
'01' => 20,
|
||||
'02' => 20,
|
||||
'03' => 20,
|
||||
'04' => 20,
|
||||
'05' => 20,
|
||||
],
|
||||
'semantic_map' => self::$MODULE_ITEM_WEIGHT,
|
||||
'shell_rules' => self::$ITEM_SHELL_MAP,
|
||||
],
|
||||
],
|
||||
|
||||
// 模块布局冻结(核心)
|
||||
'list_layout' => $listLayout,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 旧 cfg 补齐规则:只要缺字段就补,并标记 patched=true
|
||||
*/
|
||||
private static function patchCfgIfNeeded(array $cfg, string $host, int $seed): array
|
||||
{
|
||||
$patched = false;
|
||||
|
||||
// meta
|
||||
if (!isset($cfg['meta'])) {
|
||||
$cfg['meta'] = [];
|
||||
$patched = true;
|
||||
}
|
||||
if (!isset($cfg['meta']['host'])) {
|
||||
$cfg['meta']['host'] = $host;
|
||||
$patched = true;
|
||||
}
|
||||
if (!isset($cfg['meta']['seed'])) {
|
||||
$cfg['meta']['seed'] = $seed;
|
||||
$patched = true;
|
||||
}
|
||||
if (!isset($cfg['meta']['dom_prefix'])) {
|
||||
$cfg['meta']['dom_prefix'] = substr(md5($host . '_dom'), 0, 6);
|
||||
$patched = true;
|
||||
}
|
||||
if (!isset($cfg['meta']['static_hash'])) {
|
||||
$cfg['meta']['static_hash'] = substr(md5($host . '_v2025'), 0, 10);
|
||||
$patched = true;
|
||||
}
|
||||
if (!isset($cfg['meta']['version'])) {
|
||||
$cfg['meta']['version'] = 4;
|
||||
$patched = true;
|
||||
}
|
||||
|
||||
// global
|
||||
if (!isset($cfg['global'])) {
|
||||
$cfg['global'] = [];
|
||||
$patched = true;
|
||||
}
|
||||
if (!isset($cfg['global']['page_max_width_pc'])) {
|
||||
$cfg['global']['page_max_width_pc'] = self::$WIDTH_POOL[$seed % count(self::$WIDTH_POOL)];
|
||||
$patched = true;
|
||||
}
|
||||
if (!isset($cfg['global']['grid'])) {
|
||||
$cfg['global']['grid'] = self::buildGridLayout($seed);
|
||||
$patched = true;
|
||||
}
|
||||
if (!isset($cfg['global']['theme'])) {
|
||||
$cfg['global']['theme'] = [
|
||||
'mode' => ['A', 'B', 'C', 'D'][$seed % 4],
|
||||
'variant' => ($seed % 4) + 1,
|
||||
];
|
||||
$patched = true;
|
||||
}
|
||||
|
||||
// pages
|
||||
if (!isset($cfg['pages'])) {
|
||||
$cfg['pages'] = [];
|
||||
$patched = true;
|
||||
}
|
||||
if (!isset($cfg['pages']['home'])) {
|
||||
$cfg['pages']['home'] = self::buildHomePageCfg($seed);
|
||||
$patched = true;
|
||||
}
|
||||
if (!isset($cfg['pages']['category'])) {
|
||||
$cfg['pages']['category'] = self::buildCategoryPageCfg($seed);
|
||||
$patched = true;
|
||||
}
|
||||
|
||||
// components
|
||||
if (!isset($cfg['components'])) {
|
||||
$cfg['components'] = [];
|
||||
$patched = true;
|
||||
}
|
||||
if (!isset($cfg['components']['templates'])) {
|
||||
$idx = fn($shift) => (($seed >> $shift) % 5) + 1;
|
||||
$cfg['components']['templates'] = [
|
||||
'head_tpl' => $idx(0),
|
||||
'foot_tpl' => $idx(1),
|
||||
'banner_tpl' => $idx(2),
|
||||
'list_tpl' => $idx(3),
|
||||
'detail_tpl' => $idx(9),
|
||||
'play_tpl' => $idx(10),
|
||||
'recommend_tpl' => $idx(4),
|
||||
'trending_tpl' => $idx(5),
|
||||
'newest_tpl' => $idx(6),
|
||||
'ranking_tpl' => $idx(7),
|
||||
'category_tpl' => $idx(8),
|
||||
];
|
||||
$patched = true;
|
||||
}
|
||||
if (!isset($cfg['components']['list'])) {
|
||||
$cfg['components']['list'] = [
|
||||
'item_variants' => ['01'=>20,'02'=>20,'03'=>20,'04'=>20,'05'=>20],
|
||||
'semantic_map' => self::$MODULE_ITEM_WEIGHT,
|
||||
'shell_rules' => self::$ITEM_SHELL_MAP,
|
||||
];
|
||||
$patched = true;
|
||||
}
|
||||
|
||||
// list_layout(冻结默认模块布局)
|
||||
if (!isset($cfg['list_layout']) || !is_array($cfg['list_layout'])) {
|
||||
$cfg['list_layout'] = self::buildDefaultListLayouts(
|
||||
$seed,
|
||||
$cfg['pages']['home'],
|
||||
$cfg['pages']['category']
|
||||
);
|
||||
$patched = true;
|
||||
} else {
|
||||
// 补齐缺少的默认模块布局(升级时很关键:你说的“分类页不写入”就靠这里补)
|
||||
$needModules = self::collectNeededModules($cfg['pages']['home'], $cfg['pages']['category']);
|
||||
foreach ($needModules as $m) {
|
||||
if (!isset($cfg['list_layout'][$m])) {
|
||||
$cfg['list_layout'][$m] = self::buildListCfgForModule($seed + crc32($m), $m, $cfg);
|
||||
$patched = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [$cfg, $patched];
|
||||
}
|
||||
|
||||
/* ======================================================
|
||||
* Runtime Payload(TpStyle)
|
||||
* ====================================================== */
|
||||
private static function buildRuntimePayload(array $cfg, string $host, int $seed, $domainRow): array
|
||||
{
|
||||
$domPrefix = $cfg['meta']['dom_prefix'];
|
||||
$staticHash = $cfg['meta']['static_hash'];
|
||||
|
||||
// 运行时主题色(从冻结的 mode/variant 计算)
|
||||
$themeMode = $cfg['global']['theme']['mode'] ?? 'A';
|
||||
$themeVariant = (int)($cfg['global']['theme']['variant'] ?? 1);
|
||||
$themeColors = self::buildTheme($seed, $themeMode, $themeVariant);
|
||||
|
||||
// templates(路径输出保持你旧版结构)
|
||||
$tpl = $cfg['components']['templates'];
|
||||
$templates = [
|
||||
'head' => "head/head_" . sprintf("%02d", (int)$tpl['head_tpl']),
|
||||
'foot' => "footer/footer_" . sprintf("%02d", (int)$tpl['foot_tpl']),
|
||||
'banner' => "module/banner/banner_" . sprintf("%02d", (int)$tpl['banner_tpl']),
|
||||
'list' => "module/list/list_" . sprintf("%02d", (int)$tpl['list_tpl']),
|
||||
'detail' => "module/detail/detail_" . sprintf("%02d", (int)$tpl['detail_tpl']),
|
||||
'play' => "module/play/play_" . sprintf("%02d", (int)$tpl['play_tpl']),
|
||||
|
||||
// 旧系统模块(保留)
|
||||
'recommend' => "module/recommend/recommend_" . sprintf("%02d", (int)$tpl['recommend_tpl']),
|
||||
'trending' => "module/trending/trending_" . sprintf("%02d", (int)$tpl['trending_tpl']),
|
||||
'newest' => "module/newest/newest_" . sprintf("%02d", (int)$tpl['newest_tpl']),
|
||||
'ranking' => "module/ranking/ranking_" . sprintf("%02d", (int)$tpl['ranking_tpl']),
|
||||
'category' => "module/category/category_" . sprintf("%02d", (int)$tpl['category_tpl']),
|
||||
];
|
||||
|
||||
// css_files(你当前只要 head/footer/banner/list/detail/play + 可选模块 css)
|
||||
$cssFiles = [
|
||||
"head/head_" . sprintf("%02d", (int)$tpl['head_tpl']) . ".css",
|
||||
"footer/footer_" . sprintf("%02d", (int)$tpl['foot_tpl']) . ".css",
|
||||
"banner/banner_" . sprintf("%02d", (int)$tpl['banner_tpl']) . ".css",
|
||||
"list/list_" . sprintf("%02d", (int)$tpl['list_tpl']) . ".css",
|
||||
"detail/detail_" . sprintf("%02d", (int)$tpl['detail_tpl']) . ".css",
|
||||
"play/play_" . sprintf("%02d", (int)$tpl['play_tpl']) . ".css",
|
||||
];
|
||||
$cssFiles = array_values(array_unique($cssFiles));
|
||||
|
||||
return [
|
||||
'host' => $host,
|
||||
'seed' => $seed,
|
||||
'dom_prefix' => $domPrefix,
|
||||
'static_hash' => $staticHash,
|
||||
|
||||
// cfg 全量给模板(你现在模板一直在用)
|
||||
'template_cfg' => $cfg,
|
||||
|
||||
// 兼容你现在模板里直接用的快捷字段
|
||||
'page_max_width_pc' => $cfg['global']['page_max_width_pc'],
|
||||
'list_layout' => $cfg['list_layout'],
|
||||
'category_page' => $cfg['pages']['category'],
|
||||
|
||||
// 原结构
|
||||
'templates' => $templates,
|
||||
'css_files' => $cssFiles,
|
||||
] + $themeColors;
|
||||
}
|
||||
|
||||
/* ======================================================
|
||||
* Pages cfg(冻结规则池 + 稳定映射)
|
||||
* ====================================================== */
|
||||
private static function buildHomePageCfg(int $seed): array
|
||||
{
|
||||
$modulePool = ['newest', 'hot', 'rank', 'recommend', 'trending'];
|
||||
$shuffled = self::shuffleStable($modulePool, $seed + 101);
|
||||
|
||||
// 每域名固定 3~5
|
||||
$count = 3 + ($seed % 3);
|
||||
|
||||
return [
|
||||
'top_modules' => array_slice($shuffled, 0, $count),
|
||||
];
|
||||
}
|
||||
|
||||
private static function buildCategoryPageCfg(int $seed): array
|
||||
{
|
||||
return [
|
||||
'top_block' => self::buildCategoryTopBlock($seed),
|
||||
'sections_pool' => self::buildCategorySectionsPool($seed),
|
||||
];
|
||||
}
|
||||
|
||||
private static function buildCategoryTopBlock(int $seed): array
|
||||
{
|
||||
$modules = ['newest', 'hot', 'rank'];
|
||||
return ['module' => $modules[$seed % count($modules)]];
|
||||
}
|
||||
|
||||
private static function buildCategorySectionsPool(int $seed): array
|
||||
{
|
||||
$basePool = [
|
||||
['shell' => 'A', 'item' => '01', 'title' => 'F', 'class' => 'compact'],
|
||||
['shell' => 'B', 'item' => '01', 'title' => 'F', 'class' => ''],
|
||||
['shell' => 'B', 'item' => '02', 'title' => 'F', 'class' => 'loose'],
|
||||
['shell' => 'C', 'item' => '05', 'title' => 'F', 'class' => 'top compact'],
|
||||
];
|
||||
|
||||
$pool = self::shuffleStable($basePool, $seed + 301);
|
||||
$count = 3 + ($seed % 3); // 3~5(basePool 只有4条,这里实际最大=4;你以后扩展 basePool 即可>5)
|
||||
|
||||
return array_slice($pool, 0, min($count, count($pool)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 供模板调用:二级分类块 cfg(不再在模板里写 crc32 逻辑)
|
||||
*
|
||||
* 用法(模板里):
|
||||
* <?php $cfg = \app\common\helper\SiteStyle::buildCategorySectionCfg($TpStyle['template_cfg'], $arrChildrenCategory['v_category_en'], $intChildrenKey); ?>
|
||||
*/
|
||||
public static function buildCategorySectionCfg(array $cfg, string $subCategoryEn, int $order = 0): array
|
||||
{
|
||||
$seed = (int)($cfg['meta']['seed'] ?? 0)
|
||||
+ crc32($subCategoryEn)
|
||||
+ $order * 13;
|
||||
|
||||
$pool = $cfg['pages']['category']['sections_pool'] ?? [];
|
||||
if (empty($pool)) {
|
||||
// 兜底
|
||||
$pool = self::buildCategorySectionsPool($seed);
|
||||
}
|
||||
|
||||
$base = $pool[$seed % count($pool)];
|
||||
|
||||
$item = (string)$base['item'];
|
||||
$itemType = self::$ITEM_TYPE_MAP[$item] ?? 'poster';
|
||||
|
||||
$variantCount = (int)($cfg['components']['list']['item_variants'][$item] ?? 1);
|
||||
$variant = $variantCount > 0 ? ($seed % $variantCount) : 0;
|
||||
|
||||
return [
|
||||
'shell' => $base['shell'],
|
||||
'item' => $item,
|
||||
'item_type' => $itemType,
|
||||
'item_variant' => $variant,
|
||||
'title' => $base['title'],
|
||||
'class' => $base['class'],
|
||||
'grid' => $cfg['global']['grid'],
|
||||
];
|
||||
}
|
||||
|
||||
/* ======================================================
|
||||
* list_layout(冻结模块默认布局)
|
||||
* ====================================================== */
|
||||
private static function buildDefaultListLayouts(int $seed, array $homePage, array $categoryPage): array
|
||||
{
|
||||
$needModules = self::collectNeededModules($homePage, $categoryPage);
|
||||
|
||||
$out = [];
|
||||
foreach ($needModules as $m) {
|
||||
$out[$m] = self::buildListCfgForModule($seed + crc32($m), $m, [
|
||||
'global' => ['grid' => self::buildGridLayout($seed)],
|
||||
'components' => ['list' => ['item_variants' => ['01'=>20,'02'=>20,'03'=>20,'04'=>20,'05'=>20]]],
|
||||
]);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
private static function collectNeededModules(array $homePage, array $categoryPage): array
|
||||
{
|
||||
$modules = [];
|
||||
|
||||
// home 顶部模块
|
||||
foreach (($homePage['top_modules'] ?? []) as $m) {
|
||||
$modules[] = $m;
|
||||
}
|
||||
|
||||
// category 顶部块模块
|
||||
if (!empty($categoryPage['top_block']['module'])) {
|
||||
$modules[] = $categoryPage['top_block']['module'];
|
||||
}
|
||||
|
||||
// 常见页面:你后面肯定会用到(先冻结)
|
||||
$modules[] = 'category_list';
|
||||
$modules[] = 'search_list';
|
||||
|
||||
// 去重
|
||||
$modules = array_values(array_unique(array_filter($modules)));
|
||||
return $modules;
|
||||
}
|
||||
|
||||
private static function buildListCfgForModule(int $seed, string $module, array $cfg): array
|
||||
{
|
||||
$layout = self::buildListLayout($seed, $module);
|
||||
|
||||
// Title
|
||||
$layout['title'] = self::pickTitleTpl($seed + 17, $module);
|
||||
$layout['title_text'] = self::buildTitleText($seed + 19, $module);
|
||||
|
||||
// Behavior
|
||||
$behavior = self::buildListBehavior($seed + 23, $module);
|
||||
$layout['behavior'] = $behavior;
|
||||
$layout['class'] = trim(implode(' ', array_filter($behavior)));
|
||||
|
||||
// item variant(冻结)
|
||||
$layout['item_variant'] = self::pickItemVariant(
|
||||
$seed + 31,
|
||||
$layout['item'],
|
||||
$module
|
||||
);
|
||||
|
||||
// grid(冻结引用全局)
|
||||
$layout['grid'] = $cfg['global']['grid'] ?? self::buildGridLayout($seed);
|
||||
|
||||
return $layout;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 List 布局(带语义权重 + 合法映射)
|
||||
*/
|
||||
private static function buildListLayout(int $seed, string $module): array
|
||||
{
|
||||
// Item(语义优先)
|
||||
$itemIdx = self::pickItemBySemantic($module, $seed);
|
||||
$itemType = self::$ITEM_TYPE_MAP[$itemIdx] ?? 'poster';
|
||||
|
||||
// Shell(只在合法池中选)
|
||||
$allowedShells = self::$ITEM_SHELL_MAP[$itemType] ?? ['B'];
|
||||
$shell = $allowedShells[($seed >> 4) % count($allowedShells)];
|
||||
|
||||
// Title(只是一个默认值,最终会被 pickTitleTpl 覆盖)
|
||||
$titlePool = ['A', 'B', 'C', 'D', 'E'];
|
||||
$title = $titlePool[($seed >> 6) % count($titlePool)];
|
||||
|
||||
return [
|
||||
'module' => $module,
|
||||
'item' => $itemIdx,
|
||||
'item_type' => $itemType,
|
||||
'shell' => $shell,
|
||||
'title' => $title,
|
||||
'class' => '', // 最终由 buildListBehavior 生成
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* item variant:由模块/seed 稳定取值
|
||||
*/
|
||||
private static function pickItemVariant(int $seed, string $item, string $scope = 'list'): int
|
||||
{
|
||||
$count = 20;
|
||||
$salt = crc32($scope);
|
||||
return abs($seed + $salt) % $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表行为(Behavior)
|
||||
*/
|
||||
private static function buildListBehavior(int $seed, string $module): array
|
||||
{
|
||||
$behavior = [];
|
||||
|
||||
switch ($module) {
|
||||
case 'newest':
|
||||
$behavior[] = ($seed % 2 === 0) ? 'compact' : '';
|
||||
$behavior[] = ($seed % 2 === 1) ? 'collapsed' : '';
|
||||
break;
|
||||
|
||||
case 'hot':
|
||||
case 'trending':
|
||||
case 'recommend':
|
||||
$behavior[] = ($seed % 3 === 0) ? 'loose' : '';
|
||||
$behavior[] = ($seed % 2 === 0) ? 'emphasis' : '';
|
||||
break;
|
||||
|
||||
case 'rank':
|
||||
$behavior[] = 'top';
|
||||
$behavior[] = 'compact';
|
||||
break;
|
||||
|
||||
default:
|
||||
// category_list / search_list 等兜底
|
||||
$behavior[] = ($seed % 2 === 0) ? 'compact' : '';
|
||||
}
|
||||
|
||||
return array_values(array_filter($behavior));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据模块语义 + seed 选择 Item(带权重)
|
||||
*/
|
||||
private static function pickItemBySemantic(string $module, int $seed): string
|
||||
{
|
||||
$conf = self::$MODULE_ITEM_WEIGHT[$module] ?? null;
|
||||
|
||||
if (!$conf) {
|
||||
$pool = array_keys(self::$ITEM_TYPE_MAP);
|
||||
return $pool[$seed % count($pool)];
|
||||
}
|
||||
|
||||
// primary 75%
|
||||
if (($seed & 0b11) !== 0) {
|
||||
return $conf['primary'][$seed % count($conf['primary'])];
|
||||
}
|
||||
|
||||
// secondary
|
||||
if (!empty($conf['secondary']) && (($seed >> 2) & 1)) {
|
||||
return $conf['secondary'][$seed % count($conf['secondary'])];
|
||||
}
|
||||
|
||||
return $conf['fallback'][0];
|
||||
}
|
||||
|
||||
/* ======================================================
|
||||
* Grid(冻结)
|
||||
* ====================================================== */
|
||||
private static function pickPcCols(int $seed, string $size): int
|
||||
{
|
||||
$pool = self::$PC_COLS_MAP[$size] ?? [3];
|
||||
return $pool[$seed % count($pool)];
|
||||
}
|
||||
|
||||
private static function buildGridLayout(int $seed): array
|
||||
{
|
||||
return [
|
||||
// H5:1–3 列
|
||||
'cols_h5' => [1, 2, 3][$seed % 3],
|
||||
// PC:按断点
|
||||
'cols_pc_sm' => self::pickPcCols($seed + 11, 'sm'),
|
||||
'cols_pc_md' => self::pickPcCols($seed + 23, 'md'),
|
||||
'cols_pc_lg' => self::pickPcCols($seed + 37, 'lg'),
|
||||
];
|
||||
}
|
||||
|
||||
/* ======================================================
|
||||
* Title 文案池
|
||||
* ====================================================== */
|
||||
private static function mapTitleType(string $module): string
|
||||
{
|
||||
// 你要求:category/search/recommend 可复用 newest 的文案池
|
||||
return match ($module) {
|
||||
'category_list', 'search_list', 'recommend' => 'newest',
|
||||
default => $module,
|
||||
};
|
||||
}
|
||||
|
||||
private static function pickStable(array $list, int $seed)
|
||||
{
|
||||
if (empty($list)) return null;
|
||||
$seed = abs($seed);
|
||||
return $list[$seed % count($list)];
|
||||
}
|
||||
|
||||
private static function buildTitleText(int $seed, string $module): array
|
||||
{
|
||||
$type = self::mapTitleType($module);
|
||||
if (!isset(self::$TITLE_POOL[$type])) {
|
||||
$type = 'newest';
|
||||
}
|
||||
$pool = self::$TITLE_POOL[$type];
|
||||
|
||||
return [
|
||||
'primary' => self::pickStable($pool['primary'], $seed),
|
||||
'secondary' => self::pickStable($pool['secondary'], $seed >> 2),
|
||||
'seo' => self::pickStable($pool['seo'], $seed >> 4),
|
||||
];
|
||||
}
|
||||
|
||||
private static function pickTitleTpl(int $seed, string $module): string
|
||||
{
|
||||
$map = [
|
||||
'newest' => ['A', 'B', 'D'],
|
||||
'hot' => ['A', 'C', 'D', 'B'],
|
||||
'rank' => ['E', 'D'],
|
||||
'trending' => ['A', 'C', 'D'],
|
||||
'recommend' => ['A', 'B', 'C'],
|
||||
'category_list'=> ['F', 'A'],
|
||||
'search_list' => ['A', 'B'],
|
||||
];
|
||||
|
||||
$list = $map[$module] ?? ['A'];
|
||||
return $list[$seed % count($list)];
|
||||
}
|
||||
|
||||
/* ======================================================
|
||||
* DB / JSON
|
||||
* ====================================================== */
|
||||
private static function readDbConfig($domainRow): ?array
|
||||
{
|
||||
try {
|
||||
if (!$domainRow) return null;
|
||||
|
||||
$raw = is_array($domainRow)
|
||||
? ($domainRow['t_cfg'] ?? null)
|
||||
: ($domainRow->t_cfg ?? null);
|
||||
|
||||
if (empty($raw)) return null;
|
||||
|
||||
return json_decode($raw, true) ?: null;
|
||||
} catch (\Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static function writeDbConfig($domainRow, array $cfg): void
|
||||
{
|
||||
if (!$domainRow || is_array($domainRow)) return;
|
||||
|
||||
try {
|
||||
$domainRow->t_cfg = json_encode($cfg, JSON_UNESCAPED_UNICODE);
|
||||
$domainRow->save();
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
}
|
||||
|
||||
private static function readLocalJson(string $host): ?array
|
||||
{
|
||||
$file = root_path() . "storage/theme_cache/{$host}.json";
|
||||
if (!is_file($file)) return null;
|
||||
|
||||
return json_decode(file_get_contents($file), true) ?: null;
|
||||
}
|
||||
|
||||
private static function writeLocalJson(string $host, array $cfg): void
|
||||
{
|
||||
$dir = root_path() . "storage/theme_cache/";
|
||||
if (!is_dir($dir)) @mkdir($dir, 0755, true);
|
||||
|
||||
file_put_contents(
|
||||
$dir . "{$host}.json",
|
||||
json_encode($cfg, JSON_UNESCAPED_UNICODE)
|
||||
);
|
||||
}
|
||||
|
||||
/* ======================================================
|
||||
* 稳定洗牌
|
||||
* ====================================================== */
|
||||
private static function shuffleStable(array $arr, int $seed): array
|
||||
{
|
||||
$result = $arr;
|
||||
$rand = $seed;
|
||||
|
||||
for ($i = count($result) - 1; $i > 0; $i--) {
|
||||
$rand = ($rand * 31 + 17) & 0x7fffffff;
|
||||
$j = $rand % ($i + 1);
|
||||
[$result[$i], $result[$j]] = [$result[$j], $result[$i]];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/* ======================================================
|
||||
* 主题色(沿用你原来的 buildTheme + 辅助函数)
|
||||
* ====================================================== */
|
||||
private static function buildTheme(int $seed, string $mode, int $variant): array
|
||||
{
|
||||
$base = $seed + $variant * 97;
|
||||
if ($mode === 'D') $mode = ['A', 'B', 'C'][$base % 3];
|
||||
|
||||
$h = $base % 360;
|
||||
|
||||
switch ($mode) {
|
||||
case 'A':
|
||||
$primary = self::hslToHex($h, 78, 55);
|
||||
$secondary = self::hslToHex($h + 30, 65, 50);
|
||||
$accent = self::hslToHex($h + 310, 85, 56);
|
||||
$bg = '#F8FAFC';
|
||||
$bgSoft = '#F1F5F9';
|
||||
break;
|
||||
|
||||
case 'B':
|
||||
$primary = self::hslToHex($h, 55, 60);
|
||||
$secondary = self::hslToHex($h + 40, 40, 65);
|
||||
$accent = self::hslToHex($h + 300, 45, 58);
|
||||
$bg = '#FBFBFE';
|
||||
$bgSoft = '#F5F5FA';
|
||||
break;
|
||||
|
||||
case 'C':
|
||||
$primary = self::hslToHex($h, 80, 55);
|
||||
$secondary = self::hslToHex($h + 200, 60, 50);
|
||||
$accent = self::hslToHex($h + 40, 75, 60);
|
||||
$bg = self::hslToHex($h + 190, 30, 12);
|
||||
$bgSoft = self::hslToHex($h + 190, 25, 16);
|
||||
break;
|
||||
|
||||
default:
|
||||
$primary = self::hslToHex($h, 55, 60);
|
||||
$secondary = self::hslToHex($h + 40, 40, 65);
|
||||
$accent = self::hslToHex($h + 300, 45, 58);
|
||||
$bg = '#FBFBFE';
|
||||
$bgSoft = '#F5F5FA';
|
||||
}
|
||||
|
||||
$dark = self::isDarkColor($bg);
|
||||
|
||||
return [
|
||||
'color_primary' => $primary,
|
||||
'color_secondary' => $secondary,
|
||||
'color_accent' => $accent,
|
||||
'color_bg' => $bg,
|
||||
'color_bg_soft' => $bgSoft,
|
||||
'color_text_primary' => $dark ? '#FFF' : '#111',
|
||||
'color_text_secondary' => $dark ? '#CCC' : '#666',
|
||||
'color_border' => $dark ? 'rgba(255,255,255,.1)' : 'rgba(0,0,0,.1)',
|
||||
'color_grad_start' => $primary,
|
||||
'color_grad_end' => $secondary,
|
||||
'shadow_color' => $dark ? 'rgba(0,0,0,.7)' : 'rgba(0,0,0,.12)',
|
||||
'is_dark_mode' => $dark,
|
||||
];
|
||||
}
|
||||
|
||||
private static function hslToHex($h, $s, $l): string
|
||||
{
|
||||
$h /= 360;
|
||||
$s /= 100;
|
||||
$l /= 100;
|
||||
$c = (1 - abs(2 * $l - 1)) * $s;
|
||||
$x = $c * (1 - abs(fmod($h * 6, 2) - 1));
|
||||
$m = $l - $c / 2;
|
||||
|
||||
if ($h < 1 / 6) [$r, $g, $b] = [$c, $x, 0];
|
||||
elseif ($h < 2 / 6) [$r, $g, $b] = [$x, $c, 0];
|
||||
elseif ($h < 3 / 6) [$r, $g, $b] = [0, $c, $x];
|
||||
elseif ($h < 4 / 6) [$r, $g, $b] = [0, $x, $c];
|
||||
elseif ($h < 5 / 6) [$r, $g, $b] = [$x, 0, $c];
|
||||
else [$r, $g, $b] = [$c, 0, $x];
|
||||
|
||||
$r = round(($r + $m) * 255);
|
||||
$g = round(($g + $m) * 255);
|
||||
$b = round(($b + $m) * 255);
|
||||
return sprintf("#%02X%02X%02X", $r, $g, $b);
|
||||
}
|
||||
|
||||
private static function isDarkColor(string $hex): bool
|
||||
{
|
||||
$hex = ltrim($hex, '#');
|
||||
$r = hexdec(substr($hex, 0, 2));
|
||||
$g = hexdec(substr($hex, 2, 2));
|
||||
$b = hexdec(substr($hex, 4, 2));
|
||||
|
||||
$lum = (0.2126 * $r + 0.7152 * $g + 0.0722 * $b) / 255;
|
||||
return $lum < 0.45;
|
||||
}
|
||||
|
||||
/* ======================================================
|
||||
* 常量池
|
||||
* ====================================================== */
|
||||
private static array $WIDTH_POOL = [860, 900, 960, 1000, 1080, 1140, 1200, 1280, 1360];
|
||||
|
||||
private static array $PC_COLS_MAP = [
|
||||
'sm' => [3, 4],
|
||||
'md' => [4, 5, 6],
|
||||
'lg' => [6, 7, 8, 9],
|
||||
];
|
||||
|
||||
/**
|
||||
* Shell × Item 合法组合映射(poster/media/rank)
|
||||
*/
|
||||
private static array $ITEM_SHELL_MAP = [
|
||||
'poster' => ['B', 'D'],
|
||||
'media' => ['A', 'D'],
|
||||
'rank' => ['C'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Item 编号 → Item 类型
|
||||
*/
|
||||
private static array $ITEM_TYPE_MAP = [
|
||||
'01' => 'poster',
|
||||
'02' => 'media',
|
||||
'03' => 'rank',
|
||||
'04' => 'poster',
|
||||
'05' => 'rank',
|
||||
];
|
||||
|
||||
/**
|
||||
* 模块语义 → Item 权重池
|
||||
*/
|
||||
private static array $MODULE_ITEM_WEIGHT = [
|
||||
'newest' => [
|
||||
'primary' => ['01', '04'],
|
||||
'secondary' => ['02'],
|
||||
'fallback' => ['01'],
|
||||
],
|
||||
'hot' => [
|
||||
'primary' => ['01', '04'],
|
||||
'secondary' => ['02'],
|
||||
'fallback' => ['01'],
|
||||
],
|
||||
'rank' => [
|
||||
'primary' => ['05'],
|
||||
'secondary' => ['03'],
|
||||
'fallback' => ['05'],
|
||||
],
|
||||
// 兜底模块
|
||||
'trending' => [
|
||||
'primary' => ['01', '04'],
|
||||
'secondary' => ['02'],
|
||||
'fallback' => ['01'],
|
||||
],
|
||||
'recommend' => [
|
||||
'primary' => ['01', '04'],
|
||||
'secondary' => ['02'],
|
||||
'fallback' => ['01'],
|
||||
],
|
||||
'category_list' => [
|
||||
'primary' => ['01'],
|
||||
'secondary' => ['02'],
|
||||
'fallback' => ['01'],
|
||||
],
|
||||
'search_list' => [
|
||||
'primary' => ['02'],
|
||||
'secondary' => ['01'],
|
||||
'fallback' => ['02'],
|
||||
],
|
||||
];
|
||||
|
||||
private static array $TITLE_POOL = [
|
||||
'newest' => [
|
||||
'primary' => ['最近更新','最新上线','新片速递','今日更新','新内容推荐'],
|
||||
'secondary' => ['第一时间为你呈现','每日持续更新','不错过任何新片','刚刚上线,抢先观看'],
|
||||
'seo' => ['最新电影电视剧更新','今日最新影视资源','新上线影视内容合集'],
|
||||
],
|
||||
'hot' => [
|
||||
'primary' => ['热门推荐','热播精选','人气必看','大家都在看'],
|
||||
'secondary' => ['近期热度持续攀升','高点击率影片推荐','口碑与热度兼具','当前最受欢迎内容'],
|
||||
'seo' => ['热门影视作品推荐','高人气电影电视剧合集','热播影视排行榜推荐'],
|
||||
],
|
||||
'rank' => [
|
||||
'primary' => ['排行榜','热度榜单','人气排行','播放榜'],
|
||||
'secondary' => ['数据实时更新','热度排序参考','近期播放趋势','高人气作品排行'],
|
||||
'seo' => ['影视排行榜前十名','热门电影电视剧排行','高播放量影视榜单'],
|
||||
],
|
||||
];
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
0
code/app/common/helper/style/BreadcrumbRule.php
Normal file
0
code/app/common/helper/style/BreadcrumbRule.php
Normal file
154
code/app/common/helper/style/DetailMainRule.php
Normal file
154
code/app/common/helper/style/DetailMainRule.php
Normal file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\helper\style;
|
||||
|
||||
class DetailMainRule
|
||||
{
|
||||
public static function build(int $seed): array
|
||||
{
|
||||
$cover = self::compose(self::coverAtoms(), $seed + 23);
|
||||
$cover['extra'] = self::fillExtra($cover['extra'], $video);
|
||||
|
||||
return [
|
||||
'blocks' => [
|
||||
'title' => self::compose(self::titleAtoms(), $seed + 11),
|
||||
'cover' => $cover,
|
||||
'meta' => self::compose(self::metaAtoms(), $seed + 37),
|
||||
'desc' => self::compose(self::descAtoms(), $seed + 51),
|
||||
'action' => self::compose(self::actionAtoms(), $seed + 67),
|
||||
],
|
||||
'order' => self::pickOrder($seed),
|
||||
];
|
||||
}
|
||||
|
||||
/* ========= 原子池 ========= */
|
||||
|
||||
public static function titleAtoms(): array
|
||||
{
|
||||
return [
|
||||
'tag' => ['h1', 'h2'],
|
||||
'wrap' => ['none', 'header', 'div'],
|
||||
'show_year' => [true, false],
|
||||
'emphasis' => ['none', 'strong', 'em'],
|
||||
];
|
||||
}
|
||||
|
||||
public static function metaAtoms(): array
|
||||
{
|
||||
return [
|
||||
'layout' => ['ul', 'dl', 'inline'],
|
||||
'fields' => [
|
||||
['category', 'year', 'area'],
|
||||
['year', 'category'],
|
||||
['area', 'year'],
|
||||
],
|
||||
'separator' => [' / ', ' · '],
|
||||
];
|
||||
}
|
||||
|
||||
public static function descAtoms(): array
|
||||
{
|
||||
return [
|
||||
'container' => ['p', 'div', 'section'],
|
||||
'mode' => ['full', 'short'],
|
||||
'expandable' => [true, false],
|
||||
];
|
||||
}
|
||||
|
||||
public static function actionAtoms(): array
|
||||
{
|
||||
return [
|
||||
'type' => ['link', 'button', 'nav'],
|
||||
'show_hint' => [true, false],
|
||||
'text' => ['立即播放', '在线观看', '免费播放'],
|
||||
];
|
||||
}
|
||||
|
||||
// Cover Block 原子池
|
||||
public static function coverAtoms(): array
|
||||
{
|
||||
return [
|
||||
// 外层容器结构
|
||||
'container' => [
|
||||
'div.dm-cover',
|
||||
'figure.dm-cover',
|
||||
'div.dm-cover-bg',
|
||||
'section.dm-cover',
|
||||
],
|
||||
|
||||
// 图片表现形式
|
||||
'image' => [
|
||||
'img', // <img>
|
||||
'bg', // background-image
|
||||
],
|
||||
|
||||
// 附加元素
|
||||
'extra' => [
|
||||
'',
|
||||
'<span class="label">{remarks}</span>',
|
||||
'<figcaption>{remarks}</figcaption>',
|
||||
],
|
||||
|
||||
// 比例控制(CSS class)
|
||||
'ratio' => [
|
||||
'r23', // 2:3
|
||||
'r34', // 3:4
|
||||
'r11', // 1:1
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/* ========= 组合器 ========= */
|
||||
|
||||
protected static function compose(array $atoms, int $seed): array
|
||||
{
|
||||
$pick = function (?array $list, int $shift, $default = null) use ($seed) {
|
||||
if (empty($list)) {
|
||||
return $default;
|
||||
}
|
||||
return $list[($seed >> $shift) % count($list)];
|
||||
};
|
||||
|
||||
return [
|
||||
'container' => $pick($atoms['container'] ?? null, 0),
|
||||
'image' => $pick($atoms['image'] ?? null, 2, 'img'),
|
||||
'extra' => $pick($atoms['extra'] ?? null, 4, ''),
|
||||
'ratio' => $pick($atoms['ratio'] ?? null, 6, ''),
|
||||
'fields' => $pick($atoms['fields'] ?? null, 8, []),
|
||||
'mode' => $pick($atoms['mode'] ?? null, 10, 'raw'),
|
||||
'trigger' => $pick($atoms['trigger'] ?? null, 12),
|
||||
'text' => $pick($atoms['text'] ?? null, 14, ''),
|
||||
'separator' => $pick($atoms['separator'] ?? null, 16, ' '),
|
||||
'expand' => $pick($atoms['expand'] ?? null, 18, false),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
protected static function pickOrder(int $seed): array
|
||||
{
|
||||
$orders = [
|
||||
['title', 'cover', 'meta', 'desc', 'action'],
|
||||
['cover', 'title', 'meta', 'desc'],
|
||||
['title', 'meta', 'desc', 'cover', 'action'],
|
||||
['meta', 'title', 'cover', 'desc'],
|
||||
];
|
||||
return $orders[$seed % count($orders)];
|
||||
}
|
||||
|
||||
protected static function fillExtra(string $extra, array $video): string
|
||||
{
|
||||
if ($extra === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return str_replace(
|
||||
['{remarks}', '{year}', '{name}'],
|
||||
[
|
||||
$video['v_remarks'] ?? '',
|
||||
$video['v_year'] ?? '',
|
||||
$video['v_name'] ?? '',
|
||||
],
|
||||
$extra
|
||||
);
|
||||
}
|
||||
}
|
||||
0
code/app/common/helper/style/ListRule.php
Normal file
0
code/app/common/helper/style/ListRule.php
Normal file
0
code/app/common/helper/style/PlayLineRule.php
Normal file
0
code/app/common/helper/style/PlayLineRule.php
Normal file
0
code/app/common/helper/style/SeoWordsRule.php
Normal file
0
code/app/common/helper/style/SeoWordsRule.php
Normal file
Reference in New Issue
Block a user