diff --git a/code/app/common/helper/CssBuilder.php b/code/app/common/helper/CssBuilder.php
new file mode 100644
index 0000000..9b8f827
--- /dev/null
+++ b/code/app/common/helper/CssBuilder.php
@@ -0,0 +1,121 @@
+";
+ 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";
+ }
+}
diff --git a/code/app/common/helper/SeoWordsHelper.php b/code/app/common/helper/SeoWordsHelper.php
new file mode 100644
index 0000000..3c20011
--- /dev/null
+++ b/code/app/common/helper/SeoWordsHelper.php
@@ -0,0 +1,115 @@
+ $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;
+ }
+}
diff --git a/code/app/common/helper/SiteStyle-back.php b/code/app/common/helper/SiteStyle-back.php
new file mode 100644
index 0000000..8f13fe4
--- /dev/null
+++ b/code/app/common/helper/SiteStyle-back.php
@@ -0,0 +1,907 @@
+ (($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 逻辑)
+ *
+ * 用法(模板里):
+ *
+ */
+ 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' => ['影视排行榜前十名','热门电影电视剧排行','高播放量影视榜单'],
+ ],
+ ];
+}
diff --git a/code/app/common/helper/SiteStyle.php b/code/app/common/helper/SiteStyle.php
index ebb77bf..3df63c0 100644
--- a/code/app/common/helper/SiteStyle.php
+++ b/code/app/common/helper/SiteStyle.php
@@ -2,99 +2,1118 @@
namespace app\common\helper;
+use app\common\helper\style\DetailMainRule;
+
+/**
+ * =========================================================
+ * SiteStyle(最终定稿版 · 不兼容旧 cfg)
+ * ---------------------------------------------------------
+ * 职责:
+ * 1. 基于域名生成并冻结站点 cfg
+ * 2. cfg 必须完整、唯一、可持久化
+ * 3. 模板层只读 cfg,不做任何随机/计算
+ * 4. CSS 只输出文件列表,交给 CssBuilder 合并
+ * =========================================================
+ */
class SiteStyle
{
- /**
- * 站群核心配置(2025 终极无坑版)
- */
- public static function getConfig()
+ /* ======================================================
+ * 公共入口
+ * ====================================================== */
+ public static function getConfig($domainRow = null): array
{
- $host = $_SERVER['HTTP_HOST'] ?? 'default.com';
- $host = strtolower(preg_replace('/^www\./i', '', $host));
- $host = explode(':', $host)[0];
+ $host = self::resolveHost();
$seed = crc32($host);
- $themeId = ($seed % 6) + 1;
+ // 冻结 cfg(只生成一次)
+ $cfg = self::loadOrCreateCfg($domainRow, $host, $seed);
- // 正确的模块随机排序(已验证全国上万站零翻车)
- $modules = ['banner', 'recommend', 'trending', 'newest', 'ranking', 'category'];
- $moduleOrder = $modules;
+ // 运行时 payload(给模板用)
+ return self::buildRuntimePayload($cfg, $host, $seed);
+ }
- // 改用最稳定最暴力的伪随机排序方式
- $rand = $seed;
- for ($i = count($moduleOrder) - 1; $i > 0; $i--) {
- $rand = ($rand * 31 + 17) & 0x7fffffff; // 线性同余生成器
- $j = $rand % ($i + 1);
- // 交换
- $temp = $moduleOrder[$i];
- $moduleOrder[$i] = $moduleOrder[$j];
- $moduleOrder[$j] = $temp;
+ /* ======================================================
+ * 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 resolveHost(): string
+ {
+ $host = $_SERVER['HTTP_HOST'] ?? '';
+ $host = strtolower(trim($host));
+
+ // 去端口
+ $host = preg_replace('/:\d+$/', '', $host);
+
+ // IP / localhost 直接返回
+ if (
+ filter_var($host, FILTER_VALIDATE_IP) ||
+ $host === 'localhost'
+ ) {
+ return $host ?: 'default.com';
}
+ // 去 www
+ $host = preg_replace('/^www\./', '', $host);
+
+ $parts = explode('.', $host);
+ $count = count($parts);
+
+ if ($count <= 2) {
+ // example.com / test.cn
+ return $host;
+ }
+
+ // 常见二级公共后缀(可维护)
+ $secondLevelTlds = [
+ 'com.cn',
+ 'net.cn',
+ 'org.cn',
+ 'gov.cn',
+ 'co.uk',
+ 'org.uk',
+ 'ac.uk',
+ 'com.hk',
+ 'com.tw'
+ ];
+
+ $lastTwo = $parts[$count - 2] . '.' . $parts[$count - 1];
+ $lastThree = $parts[$count - 3] . '.' . $lastTwo;
+
+ if (in_array($lastTwo, $secondLevelTlds, true)) {
+ // a.b.example.com.cn → example.com.cn
+ return $parts[$count - 3] . '.' . $lastTwo;
+ }
+
+ // 默认:取最后两个
+ return $lastTwo;
+ }
+
+ private static function buildDomPrefix(string $host, int $length = 6): string
+ {
+ // 用 crc32 保证稳定
+ $num = abs(crc32($host . '_dom'));
+
+ // 字母表:首字符专用(不含数字)
+ $alpha = 'abcdefghijklmnopqrstuvwxyz';
+ $alnum = 'abcdefghijklmnopqrstuvwxyz0123456789';
+
+ // 第一个字符:只用字母
+ $prefix = $alpha[$num % 26];
+ $num = intdiv($num, 26);
+
+ // 后续字符:字母 + 数字
+ for ($i = 1; $i < $length; $i++) {
+ $prefix .= $alnum[$num % 36];
+ $num = intdiv($num, 36);
+ }
+
+ return $prefix;
+ }
+
+
+ /* ======================================================
+ * cfg 冻结层(只生成一次)
+ * ====================================================== */
+ private static function loadOrCreateCfg($domainRow, string $host, int $seed): array
+ {
+ $cfg = self::readDbConfig($domainRow)
+ ?? self::readLocalJson($host);
+
+ if ($cfg) {
+ return $cfg;
+ }
+
+ // ===== 新站:完整生成 =====
+ $cfg = self::generateFrozenCfg($host, $seed);
+
+ self::writeDbConfig($domainRow, $cfg);
+ self::writeLocalJson($host, $cfg);
+
+ return $cfg;
+ }
+
+ /**
+ * 生成完整冻结 cfg(唯一版本)
+ */
+ private static function generateFrozenCfg(string $host, int $seed): array
+ {
+ //$domPrefix = substr(md5($host . '_dom'), 0, 6);
+ $domPrefix = self::buildDomPrefix($host);
+ $staticHash = substr(md5($host . '_v2025'), 0, 10);
+
+ // 模板编号(100 套 head/footer 完全可继续用)
+ $idx = fn($shift) => (($seed >> $shift) % 100) + 1;
+
+ // 全局 grid
+ $grid = self::buildGridLayout($seed);
+
+ // 页面规则
+ $homePage = self::buildHomePageCfg($seed);
+ $categoryIndexPage = self::buildCategoryIndexPageCfg($seed);
+ $searchPage = self::buildSearchPageCfg($seed);
+ $rankHomePage = self::buildRankHomePageCfg($seed);
+
+ $listLayout = self::buildAllListLayouts($seed, $domPrefix);
+
return [
- 'theme_id' => $themeId,
- 'color_primary' => self::randColor($seed . '_pri'),
- 'color_secondary' => self::randColor($seed . '_sec'),
- 'color_accent' => self::randColor($seed . '_acc'),
- 'layout_style' => $seed % 5,
- 'module_order' => $moduleOrder,
- 'static_hash' => substr(md5($host . 'v2025'), 0, 10),
- 'is_dark_mode' => ($seed % 8 === 0),
- 'show_ads' => ($seed % 3 !== 0),
+ 'meta' => [
+ 'host' => $host,
+ 'seed' => $seed,
+ 'dom_prefix' => $domPrefix,
+ 'static_hash' => $staticHash,
+ 'version' => 1,
+ ],
+
+ 'global' => [
+ 'page_max_width_pc' => self::$WIDTH_POOL[$seed % count(self::$WIDTH_POOL)],
+ 'grid' => $grid,
+ 'theme' => [
+ 'mode' => ['A', 'B', 'C', 'D'][$seed % 4],
+ 'variant' => ($seed % 4) + 1,
+ ],
+ ],
+
+ 'pages' => [
+ 'home' => $homePage,
+ 'category_index' => $categoryIndexPage,
+ 'category' => [],
+ 'search' => $searchPage,
+ 'rank_home' => $rankHomePage,
+ 'detail' => self::buildDetailPageCfg($seed),
+ 'play' => self::buildPlayPageCfg($seed),
+ ],
+
+ 'components' => [
+ 'templates' => [
+ 'head_tpl' => $idx(0),
+ 'foot_tpl' => $idx(1),
+ 'banner_tpl' => $idx(2),
+ 'list_tpl' => $idx(3),
+ 'detail_tpl' => $idx(4),
+ 'play_tpl' => $idx(5),
+ ],
+ '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,
+ ],
+ // ========== 分页组件(全站统一) ==========
+ 'pager' => [
+ // 母型(结构级差异)
+ 'family' => [
+ 'numeric',
+ 'pill',
+ 'block',
+ 'input',
+ 'minimal',
+ ][$seed % 5],
+
+ // 子变体(HTML 结构差异)
+ 'variant' => ($seed >> 4) % 20,
+
+ // 视觉语气(CSS 层)
+ 'tone' => 1 + (($seed >> 8) % 3),
+ ],
+ ],
+
+ 'list_layout' => $listLayout,
];
}
- /**
- * 生成好看的随机色(完全不依赖 mt_srand)
- */
- private static function randColor($seedStr)
+ /* ======================================================
+ * Runtime Payload(TpStyle)
+ * ====================================================== */
+ private static function buildRuntimePayload(array $cfg, string $host, int $seed): array
{
- $hash = crc32($seedStr);
+ $domPrefix = $cfg['meta']['dom_prefix'];
+ $staticHash = $cfg['meta']['static_hash'];
- // 色相 0~359
- $h = $hash % 360;
+ // 主题色(运行时算,不冻结具体颜色)
+ $theme = self::buildTheme(
+ $seed,
+ $cfg['global']['theme']['mode'],
+ $cfg['global']['theme']['variant']
+ );
- // 饱和度 45~92%(避免太灰)
- $s = 45 + (($hash >> 8) % 48);
+ // 模板路径(你原来 include 用的方式完全保留)
+ $tpl = $cfg['components']['templates'];
+ $templates = [
+ 'head' => "head/head_" . sprintf('%02d', $tpl['head_tpl']),
+ 'foot' => "footer/footer_" . sprintf('%02d', $tpl['foot_tpl']),
+ 'banner' => "module/banner/banner_" . sprintf('%02d', $tpl['banner_tpl']),
+ 'list' => "module/list/list_" . sprintf('%02d', $tpl['list_tpl']),
+ 'detail' => "module/detail/detail_" . sprintf('%02d', $tpl['detail_tpl']),
+ 'play' => "module/play/play_" . sprintf('%02d', $tpl['play_tpl']),
+ ];
- // 亮度 38~78%(避免太暗或太刺眼)
- $l = 38 + (($hash >> 16) % 41);
+ // CSS:只负责声明模块,最终由 CssBuilder 合并
+ $cssFiles = [
+ "head/head_" . sprintf('%02d', $tpl['head_tpl']) . ".css",
+ "footer/footer_" . sprintf('%02d', $tpl['foot_tpl']) . ".css",
+ "pager/pager_" . sprintf('%02d', $cfg['components']['pager']['family']) . ".css",
+ "search/search_header_" . sprintf('%02d', $cfg['pages']['search']['top_block']['family']) . ".css",
- return self::hslToHex($h, $s, $l);
+ "detail_main/layout/layout_" . $cfg['pages']['detail']['detail_main']['layout'] . ".css",
+ "breadcrumb/layout/layout_" . $cfg['pages']['detail']['breadcrumb']['layout'] . ".css",
+ "playline/layout/layout_" . $cfg['pages']['detail']['playline']['layout'] . ".css",
+ "seowords/layout/layout_" . $cfg['pages']['detail']['seowords']['layout'] . ".css",
+
+ "banner/banner_" . sprintf('%02d', $tpl['banner_tpl']) . ".css",
+ "list/list_" . sprintf('%02d', $tpl['list_tpl']) . ".css",
+ "detail/detail_" . sprintf('%02d', $tpl['detail_tpl']) . ".css",
+ "play/play_" . sprintf('%02d', $tpl['play_tpl']) . ".css",
+ ];
+
+ return [
+ 'host' => $host,
+ 'seed' => $seed,
+ 'dom_prefix' => $domPrefix,
+ 'static_hash' => $staticHash,
+
+ 'templates' => $templates,
+ 'css_files' => $cssFiles,
+ 'template_cfg' => $cfg,
+
+ 'page_max_width_pc' => $cfg['global']['page_max_width_pc'],
+ 'list_layout' => $cfg['list_layout'],
+ 'category_index' => $cfg['pages']['category_index'],
+ ] + $theme;
+ }
+
+ /* ======================================================
+ * Page cfg
+ * ====================================================== */
+ private static function buildHomePageCfg(int $seed): array
+ {
+ $pool = ['news', 'piaofang', 'rank', 'tuijian', 'update'];
+ $pool = self::shuffleStable($pool, $seed + 101);
+
+ // 首页模块数量:1–5(站点级稳定)
+ $count = ($seed % count($pool)) + 1;
+
+ return [
+ 'modules' => array_slice($pool, 0, $count),
+ ];
+ }
+
+ private static function buildCategoryIndexPageCfg(int $seed): array
+ {
+ $pool = ['news', 'piaofang', 'rank', 'tuijian', 'update'];
+ return [
+ 'top_block' => [
+ 'module' => $pool[$seed % count($pool)],
+ ],
+ 'sections_pool' => self::buildCategorySectionsPool($seed),
+
+ ];
+ }
+
+ private static function buildSearchPageCfg(int $seed): array
+ {
+ return [
+ 'top_block' => [
+ // 母型(结构级差异)
+ 'family' => [
+ 'simple',
+ 'centered',
+ 'rich',
+ 'seo',
+ 'media',
+ ][$seed % 5],
+
+ // 子变体(HTML 结构差异)
+ 'variant' => ($seed >> 8) % 20,
+
+ // 视觉语气(CSS 层)
+ 'tone' => 1 + (($seed >> 6) % 3),
+ ],
+ ];
+ }
+
+ private static function buildRankHomePageCfg(int $seed): array
+ {
+ $out = [];
+
+ foreach (self::$RANK_HOME_MODULES as $key => $conf) {
+ $s = $seed + crc32($key);
+
+ // 1️⃣ item 只能是 rank 类
+ $rankItems = ['03', '05'];
+ $item = $rankItems[$s % count($rankItems)];
+
+ // 2️⃣ shell:只从“适合榜单的 shell 池”里选
+ // 你已有 ITEM_SHELL_MAP['rank'] => ['C']
+ $shells = self::$ITEM_SHELL_MAP['rank'];
+ $shell = $shells[$s % count($shells)];
+
+ // 3️⃣ 行为(榜单专用)
+ $class = 'rank compact top';
+
+ $out[] = [
+ 'module' => $key,
+ 'title' => $conf['title'],
+ 'sort_type' => $conf['sort_type'],
+
+ // ===== list / item 体系 =====
+ 'item' => $item,
+ 'item_type' => 'rank',
+ 'item_variant' => $s % 20,
+ 'shell' => $shell,
+ 'class' => $class,
+
+ // ===== 列表布局(完全复用)=====
+ 'grid' => self::buildGridLayout($s),
+ 'layout' => self::buildListRowsAndLimit(
+ $s,
+ self::buildGridLayout($s),
+ 'low' // 榜单首页:低密度
+ ),
+
+ // ===== 数据层 =====
+ 'limit' => 7,
+ 'more_url' => "/rank/{$conf['sort_type']}",
+ ];
+ }
+
+ return [
+ 'modules' => $out,
+ ];
+ }
+
+ private static function buildDetailPageCfg(int $seed): array
+ {
+ return [
+ // 页面模块顺序
+ 'modules' => [
+ 'breadcrumb',
+ 'detail_main',
+ 'playline',
+ 'seowords',
+ 'pinlun',
+ 'list',
+ ],
+
+ // ===== DetailMain =====
+ 'detail_main' => self::buildDetailMainCfg($seed),
+
+ // ===== PlayLine =====
+ 'playline' => [
+ 'layout' => ['A', 'B', 'C'][$seed % 3],
+ 'variant' => ($seed >> 4) % 20,
+ ],
+
+ // ===== pinlun 为后面预留 =====
+ 'pinlun' => [
+ 'layout' => ['A', 'B', 'C'][$seed % 3],
+ 'variant' => ($seed >> 4) % 20,
+ ],
+
+ // ===== Breadcrumb =====
+ 'breadcrumb' => [
+ 'layout' => ['A', 'B', 'C', 'D'][$seed % 4],
+ 'variant' => ($seed >> 4) % 20,
+ 'class' => 'breadcrumb compact seo',
+ ],
+ // ===== SeoWords =====
+ 'seowords' => [
+ 'layout' => ['A', 'B', 'C', 'D'][($seed >> 2) % 4], // 原 family
+ 'variant' => ($seed >> 8) % 20, // HTML 子变体索引(0–19)
+ 'rewrite' => ($seed >> 6) % 8, // 关键词加工策略(0–7,可扩)
+ 'class' => 'inline seo',
+ ],
+
+
+ // ===== 下方列表(复用 list 体系)=====
+ 'list' => [
+ 'modules' => self::buildDetailPageListCfg($seed),
+ ],
+ ];
+ }
+
+ private static function buildDetailPageListCfg(int $seed): array
+ {
+ $pool = ['news', 'piaofang', 'rank', 'tuijian', 'update'];
+ $pool = self::shuffleStable($pool, $seed + 101);
+
+ // 模块数量:1–3(详情页专用,克制)
+ $count = ($seed % 3) + 1;
+
+ return array_slice($pool, 0, $count);
+ }
+
+
+ private static function buildDetailMainCfg(int $seed): array
+ {
+ // ===== DOM 顺序池(真实结构差异)=====
+ $orders = [
+ ['title', 'cover', 'meta', 'desc', 'action'],
+ ['cover', 'title', 'meta', 'action', 'desc'],
+ ['meta', 'title', 'cover', 'desc', 'action'],
+ ['cover', 'meta', 'title', 'desc', 'action'],
+ ['title', 'meta', 'action', 'cover', 'desc'],
+ ['meta', 'cover', 'title', 'action', 'desc'],
+ ];
+
+ // ===== 视觉布局池(只影响 CSS)=====
+ $layouts = ['A', 'B', 'C'];
+
+ return [
+ // DOM 结构顺序(冻结)
+ 'order' => $orders[$seed % count($orders)],
+
+ // 布局风格(冻结)
+ 'layout' => $layouts[($seed >> 3) % count($layouts)],
+
+ // 五大模块各自 variant(冻结)
+ 'title' => $seed % 20,
+ 'cover' => ($seed >> 2) % 20,
+ 'meta' => ($seed >> 4) % 20,
+ 'desc' => ($seed >> 6) % 20,
+ 'action' => ($seed >> 8) % 20,
+ ];
+ }
+
+
+ private static function buildPlayPageCfg(int $seed): array
+ {
+ return [
+ // 页面模块顺序
+ 'modules' => [
+ 'breadcrumb',
+ // 'detail_main',
+ 'player',
+ 'playline',
+ 'seowords',
+ 'pinlun',
+ 'list',
+ ],
+
+ // ===== DetailMain =====
+ 'detail_main' => self::buildDetailMainCfg($seed),
+
+ // ===== player =====
+ 'player' => [
+ 'engine' => 'dplayer', // 现在固定
+ 'variant' => ($seed >> 6) % 20, // HTML 子变体
+ 'class' => 'player focus', // 行为/视觉类
+ 'opt' => [
+ 'autoplay' => false,
+ 'muted' => false,
+ 'preload' => 'metadata',
+ 'ratio' => '16x9',
+ ],
+ ],
+
+
+ // ===== PlayLine =====
+ 'playline' => [
+ 'layout' => ['A', 'B', 'C'][$seed % 3],
+ 'variant' => ($seed >> 4) % 20,
+ ],
+
+ // ===== pinlun 为后面预留 =====
+ 'pinlun' => [
+ 'layout' => ['A', 'B', 'C'][$seed % 3],
+ 'variant' => ($seed >> 4) % 20,
+ ],
+
+ // ===== Breadcrumb =====
+ 'breadcrumb' => [
+ 'layout' => ['A', 'B', 'C', 'D'][$seed % 4],
+ 'variant' => ($seed >> 4) % 20,
+ 'class' => 'breadcrumb compact seo',
+ ],
+ // ===== SeoWords =====
+ 'seowords' => [
+ 'layout' => ['A', 'B', 'C', 'D'][($seed >> 2) % 4], // 原 family
+ 'variant' => ($seed >> 8) % 20, // HTML 子变体索引(0–19)
+ 'rewrite' => ($seed >> 6) % 8, // 关键词加工策略(0–7,可扩)
+ 'class' => 'inline seo',
+ ],
+
+
+ // ===== 下方列表(复用 list 体系)=====
+ 'list' => [
+ 'modules' => self::buildDetailPageListCfg($seed),
+ ],
+ ];
+ }
+
+ private static function buildCategorySectionsPool(int $seed): array
+ {
+ $base = [
+ ['shell' => 'A', 'item' => '01', 'class' => 'compact'],
+ ['shell' => 'B', 'item' => '01', 'class' => ''],
+ ['shell' => 'B', 'item' => '02', 'class' => 'loose'],
+ ['shell' => 'D', 'item' => '04', 'class' => ''],
+ ];
+ $base = self::shuffleStable($base, $seed + 301);
+ return array_slice($base, 0, 3 + ($seed % 2));
+ }
+
+ private static function buildDetailMainBlocks(int $seed): array
+ {
+ // 每个 Block 使用哪个模板
+ $blocks = [
+ 'title' => $seed % 3 + 1,
+ 'cover' => ($seed >> 2) % 3 + 1,
+ 'meta' => ($seed >> 4) % 3 + 1,
+ 'desc' => ($seed >> 6) % 3 + 1,
+ 'action' => ($seed & 1) ? 1 : 2,
+ ];
+
+ // Block 顺序池
+ $orders = [
+ ['title', 'cover', 'meta', 'desc', 'action'],
+ ['cover', 'title', 'meta', 'desc', 'action'],
+ ['title', 'meta', 'cover', 'desc'],
+ ['meta', 'title', 'desc', 'cover'],
+ ];
+
+ return [
+ 'blocks' => $blocks,
+ 'order' => $orders[$seed % count($orders)],
+ ];
+ }
+ private static function buildTitleClampClass(int $domainSeed, string $domPrefix): string
+ {
+ $h = crc32($domainSeed . '|tclamp');
+ $pack = $h % 6; // 0..5
+ $lines = (($h >> 8) % 3) + 1; // 1..3
+
+ return $domPrefix . '-tclamp'
+ . ' ' . $domPrefix . '-tclamp-p' . $pack
+ . ' ' . $domPrefix . '-tclamp-l' . $lines;
+ }
+
+
+ /* ======================================================
+ * list_layout(一次性冻结)
+ * ====================================================== */
+ private static function buildAllListLayouts(int $seed, string $domPrefix): array
+ {
+ $modules = [
+ 'news',
+ 'piaofang',
+ 'rank',
+ 'tuijian',
+ 'update',
+ 'category_list_index',
+ 'rank_list_index',
+ 'category_list',
+ 'search_list'
+ ];
+
+ $out = [];
+ foreach ($modules as $m) {
+ $out[$m] = self::buildListLayoutForModule($seed + crc32($m), $m,$seed, $domPrefix);
+ }
+ return $out;
+ }
+
+ private static function buildListLayoutForModule(int $seed, string $module, int $domainSeed, string $domPrefix): array
+ {
+ $item = self::pickItemBySemantic($module, $seed);
+ $itemType = self::$ITEM_TYPE_MAP[$item];
+ $shells = self::$ITEM_SHELL_MAP[$itemType];
+ $shell = $shells[$seed % count($shells)];
+
+ $grid = self::buildGridLayout($seed);
+ // 页面密度:按模块语义决定
+ $density = match ($module) {
+ 'search_list' => 'high',
+ 'category_list' => 'high',
+ 'category_list_index' => 'low',
+ 'rank_list_index' => 'low',
+ 'news',
+ 'piaofang',
+ 'rank',
+ 'tuijian',
+ 'update' => 'low',
+ default => 'normal',
+ };
+ $layout = self::buildListRowsAndLimit($seed, $grid, $density);
+
+ return [
+ 'module' => $module,
+ 'item' => $item,
+ 'item_type' => $itemType,
+ 'item_variant' => $seed % 20,
+ 'shell' => $shell,
+
+ // ✅ title 母型(A/B/C/F)
+ 'title' => self::pickTitleTpl($seed + 17, $module),
+ // ✅ 行为 class(冻结)
+ //'class' => implode(' ', self::buildListBehavior($seed, $module)),
+ 'class' => trim(
+ implode(' ', self::buildListBehavior($seed, $module))
+ . ' ' . self::buildTitleClampClass($domainSeed, $domPrefix)
+ ),
+
+ // ✅ 标题文案(冻结)
+ 'title_text' => self::buildTitleText($seed + 19, $module),
+ // ✅ 原有 grid 保留
+ 'grid' => $grid,
+
+ // ✅ 新增:列表布局锚点
+ 'layout' => [
+ 'rows' => $layout['rows'],
+ 'max_items' => $layout['max_items'],
+ 'max' => $layout['max'],
+ ],
+ ];
+ }
+
+ private static function pickTitleTpl(int $seed, string $module): string
+ {
+ $map = [
+ 'news' => ['A', 'B', 'D'],
+ 'piaofang' => ['A', 'C', 'D', 'B'],
+ 'rank' => ['E', 'D'],
+ 'update' => ['A', 'C', 'D'],
+ 'tuijian' => ['A', 'B', 'C'],
+ 'category_list' => ['F', 'A'],
+ 'search_list' => ['A', 'B'],
+ ];
+
+ $list = $map[$module] ?? ['A'];
+ return $list[$seed % count($list)];
+ }
+
+
+ /**
+ * 列表行为(Behavior)
+ */
+ private static function buildListBehavior(int $seed, string $module): array
+ {
+ $behavior = [];
+
+ switch ($module) {
+ case 'news':
+ $behavior[] = ($seed % 2 === 0) ? 'compact' : '';
+ $behavior[] = ($seed % 2 === 1) ? 'collapsed' : '';
+ break;
+
+ case 'piaofang':
+ case 'update':
+ case 'tuijian':
+ $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));
}
/**
- * HSL → HEX 转换(精准无误)
+ * 构建二级分类 Section 的最终配置
+ * - 稳定
+ * - 与域名 / 分类 / 顺序强绑定
+ * - 模板层零逻辑
*/
- private static function hslToHex($h, $s, $l)
+ 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'] ?? [];
+
+ // 兜底(理论上不会走到,除非 cfg 被破坏)
+ if (empty($pool)) {
+ $pool = self::buildCategorySectionsPool($seed);
+ }
+
+ $base = $pool[$seed % count($pool)];
+
+ $item = (string)($base['item'] ?? '01');
+ $itemType = self::$ITEM_TYPE_MAP[$item] ?? 'poster';
+
+ $variantCount = (int)($cfg['components']['list']['item_variants'][$item] ?? 1);
+ $variant = $variantCount > 0 ? ($seed % $variantCount) : 0;
+
+ $domainSeed = (int)($cfg['meta']['seed'] ?? 0);
+ $domPrefix = (string)($cfg['meta']['dom_prefix'] ?? '');
+ $tc = self::buildTitleClampClass($domainSeed, $domPrefix);
+
+ return [
+ 'shell' => (string)$base['shell'],
+ 'item' => $item,
+ 'item_type' => $itemType,
+ 'item_variant' => $variant,
+ 'title' => (string)($base['title'] ?? 'F'),
+ // 'class' => (string)($base['class'] ?? ''),
+ 'class' => trim(((string)($base['class'] ?? '')) . ' ' . $tc),
+ 'grid' => $cfg['global']['grid'],
+ ];
+ }
+
+
+ /* ======================================================
+ * Helpers
+ * ====================================================== */
+ private static function pickItemBySemantic(string $module, int $seed): string
+ {
+ $conf = self::$MODULE_ITEM_WEIGHT[$module] ?? null;
+ if (!$conf) {
+ return '01';
+ }
+ if (($seed & 3) !== 0) {
+ return $conf['primary'][$seed % count($conf['primary'])];
+ }
+ return $conf['fallback'][0];
+ }
+
+ private static function buildGridLayout(int $seed): array
+ {
+ return [
+ 'cols_h5' => [1, 2, 3][$seed % 3],
+ 'cols_pc_sm' => self::$PC_COLS_MAP['sm'][$seed % 2],
+ 'cols_pc_md' => self::$PC_COLS_MAP['md'][$seed % 3],
+ 'cols_pc_lg' => self::$PC_COLS_MAP['lg'][$seed % 4],
+ ];
+ }
+ /**
+ * 构建列表行数 + 数据量(基于列数 + 页面密度)
+ * 只用于 cfg 冻结阶段
+ */
+ private static function buildListRowsAndLimit(
+ int $seed,
+ array $cols,
+ string $density
+ ): array {
+ // 兜底
+ if (!isset(self::$LIST_DENSITY_ROW_RANGE[$density])) {
+ $density = 'normal';
+ }
+
+ $range = self::$LIST_DENSITY_ROW_RANGE[$density];
+ $rows = [];
+
+ // 1️⃣ lg 先定(锚点)
+ [$lgMin, $lgMax] = $range['lg'];
+ $rows['lg'] = $lgMin + ($seed % ($lgMax - $lgMin + 1));
+
+ $maxItems = $cols['cols_pc_lg'] * $rows['lg'];
+
+ // 2️⃣ md / sm / h5:在范围内随机,但不溢出
+ foreach (['md', 'sm', 'h5'] as $bp) {
+ $colKey = match ($bp) {
+ 'md' => 'cols_pc_md',
+ 'sm' => 'cols_pc_sm',
+ 'h5' => 'cols_h5',
+ };
+
+ [$min, $max] = $range[$bp];
+ $valid = [];
+
+ for ($r = $min; $r <= $max; $r++) {
+ if ($cols[$colKey] * $r <= $maxItems) {
+ $valid[] = $r;
+ }
+ }
+
+ $rows[$bp] = !empty($valid)
+ ? $valid[$seed % count($valid)]
+ : 1;
+ }
+
+ return [
+ 'rows' => $rows,
+ 'max_items' => $maxItems,
+ 'max' => [
+ 'lg' => $cols['cols_pc_lg'] * $rows['lg'],
+ 'md' => $cols['cols_pc_md'] * $rows['md'],
+ 'sm' => $cols['cols_pc_sm'] * $rows['sm'],
+ 'h5' => $cols['cols_h5'] * $rows['h5'],
+ ],
+ ];
+ }
+
+
+ private static function shuffleStable(array $arr, int $seed): array
+ {
+ $r = $arr;
+ $n = count($r);
+ $x = $seed;
+ for ($i = $n - 1; $i > 0; $i--) {
+ $x = ($x * 31 + 17) & 0x7fffffff;
+ $j = $x % ($i + 1);
+ [$r[$i], $r[$j]] = [$r[$j], $r[$i]];
+ }
+ return $r;
+ }
+
+ /* ======================================================
+ * 主题色(沿用你原来的 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];
- }
-
+ 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];
return sprintf(
"#%02X%02X%02X",
- round(($r + $m) * 255),
- round(($g + $m) * 255),
- round(($b + $m) * 255)
+ ($r + $m) * 255,
+ ($g + $m) * 255,
+ ($b + $m) * 255
);
}
+ 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;
+ }
+
+ /* ======================================================
+ * DB / JSON
+ * ====================================================== */
+ private static function readDbConfig($domainRow): ?array
+ {
+ if (!$domainRow) return null;
+ $raw = is_array($domainRow) ? ($domainRow['t_cfg'] ?? null) : ($domainRow->t_cfg ?? null);
+ return $raw ? json_decode($raw, true) : null;
+ }
+
+ private static function writeDbConfig($domainRow, array $cfg): void
+ {
+ if (!$domainRow || is_array($domainRow)) return;
+ $domainRow->t_cfg = json_encode($cfg, JSON_UNESCAPED_UNICODE);
+ $domainRow->save();
+ }
+
+ private static function readLocalJson(string $host): ?array
+ {
+ $f = root_path() . "storage/theme_cache/{$host}.json";
+ return is_file($f) ? json_decode(file_get_contents($f), 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 buildTitleText(int $seed, string $module): array
+ {
+ $type = self::mapTitleType($module);
+ if (!isset(self::$TITLE_POOL[$type])) {
+ $type = 'news';
+ }
+ $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),
+ ];
+ }
+
+ /* ======================================================
+ * Title 文案池
+ * ====================================================== */
+ private static function mapTitleType(string $module): string
+ {
+ // 你要求:category/search/tuijian 可复用 news 的文案池
+ return match ($module) {
+ 'category_list', 'search_list', 'tuijian' => 'news',
+ 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 array $WIDTH_POOL = [960, 1080, 1200, 1280, 1360];
+
+ private static array $PC_COLS_MAP = [
+ 'sm' => [3, 4],
+ 'md' => [4, 5, 6],
+ 'lg' => [6, 7, 8, 9],
+ ];
+
+ private static array $ITEM_SHELL_MAP = [
+ // 'poster' => ['B', 'D'],
+ // 'media' => ['A', 'D'],
+ 'poster' => ['B'],
+ 'media' => ['A'],
+ 'rank' => ['C'],
+ ];
+
+ private static array $ITEM_TYPE_MAP = [
+ '01' => 'poster',
+ '02' => 'media',
+ '03' => 'rank',
+ '04' => 'poster',
+ '05' => 'rank',
+ ];
+
+ private static array $MODULE_ITEM_WEIGHT = [
+ 'news' => ['primary' => ['01', '04'], 'secondary' => ['02'], 'fallback' => ['01']],
+ 'piaofang' => ['primary' => ['01', '04'], 'secondary' => ['02'], 'fallback' => ['01']],
+ 'rank' => ['primary' => ['05'], 'secondary' => ['03'], 'fallback' => ['05']],
+ 'tuijian' => ['primary' => ['01'], 'secondary' => ['02'], 'fallback' => ['01']],
+ 'update' => ['primary' => ['01'], '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 = [
+ 'news' => [
+ 'primary' => ['最近更新', '最新上线', '新片速递', '今日更新', '新内容推荐'],
+ 'secondary' => ['第一时间为你呈现', '每日持续更新', '不错过任何新片', '刚刚上线,抢先观看'],
+ 'seo' => ['最新电影电视剧更新', '今日最新影视资源', '新上线影视内容合集'],
+ ],
+ 'update' => [
+ 'primary' => ['最近更新', '最新上线', '新片速递', '今日更新', '新内容推荐'],
+ 'secondary' => ['第一时间为你呈现', '每日持续更新', '不错过任何新片', '刚刚上线,抢先观看'],
+ 'seo' => ['最新电影电视剧更新', '今日最新影视资源', '新上线影视内容合集'],
+ ],
+ 'piaofang' => [
+ 'primary' => ['热门推荐', '热播精选', '人气必看', '大家都在看'],
+ 'secondary' => ['近期热度持续攀升', '高点击率影片推荐', '口碑与热度兼具', '当前最受欢迎内容'],
+ 'seo' => ['热门影视作品推荐', '高人气电影电视剧合集', '热播影视排行榜推荐'],
+ ],
+ 'tuijian' => [
+ 'primary' => ['热门推荐', '热播精选', '人气必看', '大家都在看'],
+ 'secondary' => ['近期热度持续攀升', '高点击率影片推荐', '口碑与热度兼具', '当前最受欢迎内容'],
+ 'seo' => ['热门影视作品推荐', '高人气电影电视剧合集', '热播影视排行榜推荐'],
+ ],
+ 'rank' => [
+ 'primary' => ['排行榜', '热度榜单', '人气排行', '播放榜'],
+ 'secondary' => ['数据实时更新', '热度排序参考', '近期播放趋势', '高人气作品排行'],
+ 'seo' => ['影视排行榜前十名', '热门电影电视剧排行', '高播放量影视榜单'],
+ ],
+ ];
+ // SiteStyle.php
+
+ protected static $LIST_DENSITY_ROW_RANGE = [
+ 'low' => [
+ 'lg' => [2, 3],
+ 'md' => [2, 3],
+ 'sm' => [2, 3],
+ 'h5' => [2, 4],
+ ],
+ 'normal' => [
+ 'lg' => [2, 4],
+ 'md' => [2, 4],
+ 'sm' => [2, 4],
+ 'h5' => [2, 4],
+ ],
+ 'high' => [
+ 'lg' => [4, 8],
+ 'md' => [4, 8],
+ 'sm' => [4, 8],
+ 'h5' => [4, 8],
+ ],
+ ];
+ private static array $RANK_HOME_MODULES = [
+ 'rank_daily' => ['title' => '日排行榜', 'sort_type' => 'daily'],
+ 'rank_weekly' => ['title' => '周排行榜', 'sort_type' => 'weekly'],
+ 'rank_monthly' => ['title' => '月排行榜', 'sort_type' => 'monthly'],
+ 'rank_total' => ['title' => '总排行榜', 'sort_type' => 'total'],
+ ];
}
diff --git a/code/app/common/helper/style/BreadcrumbRule.php b/code/app/common/helper/style/BreadcrumbRule.php
new file mode 100644
index 0000000..e69de29
diff --git a/code/app/common/helper/style/DetailMainRule.php b/code/app/common/helper/style/DetailMainRule.php
new file mode 100644
index 0000000..e62f4cc
--- /dev/null
+++ b/code/app/common/helper/style/DetailMainRule.php
@@ -0,0 +1,154 @@
+ [
+ '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', //
+ 'bg', // background-image
+ ],
+
+ // 附加元素
+ 'extra' => [
+ '',
+ '{remarks}',
+ '
+ 每天发现一点新片子 +
++ 全网影视一站式聚合 +
+热播精选 · 高清极速观看
+{$vo.v_name}
+ ++ {if !empty($node.url)} + {$node.title} + {else /} + {$node.title} + {/if} +
+ {/volist} ++ {volist name="arrBreadcrumb" id="node" key="i"} + {if !empty($node.url)} + {$node.title} + {else /} + {$node.title} + {/if} + {if $i < count($arrBreadcrumb)} / {/if} + {/volist} +
+ + {// ================= Variant 2:small ================= } + {elseif $intVariant == 2} + + {volist name="arrBreadcrumb" id="node"} + {$node.title} + {/volist} + + + {// ================= Variant 3:SEO 句子 ================= } + {elseif $intVariant == 3} ++ 当前所在位置: + {volist name="arrBreadcrumb" id="node" key="i"} + {$node.title}{if $i < count($arrBreadcrumb)} → {/if} + {/volist} +
+ + {// ================= Variant 4:time + 文本 ================= } + {elseif $intVariant == 4} + + + {// ================= Variant 5:strong 强调 ================= } + {elseif $intVariant == 5} + + {volist name="arrBreadcrumb" id="node" key="i"} + {$node.title}{if $i < count($arrBreadcrumb)} / {/if} + {/volist} + + + {// ================= Variant 6:em 斜体 ================= } + {elseif $intVariant == 6} + + {volist name="arrBreadcrumb" id="node"} + {$node.title} + {/volist} + + + {// ================= Variant 7:address ================= } + {elseif $intVariant == 7} + + {volist name="arrBreadcrumb" id="node"} + {$node.title} + {/volist} + + + {// ================= Variant 8:blockquote ================= } + {elseif $intVariant == 8} ++ {volist name="arrBreadcrumb" id="node"} + {$node.title} + {/volist} ++ + {// ================= Variant 9:正文段落 ================= } + {elseif $intVariant == 9} +
+ 本页面内容来源于 + {volist name="arrBreadcrumb" id="node" key="i"} + {$node.title}{if $i < count($arrBreadcrumb)} · {/if} + {/volist} +
+ + {// ================= Variant 10:span + a 混合 ================= } + {elseif $intVariant == 10} + + {volist name="arrBreadcrumb" id="node"} + {if !empty($node.url)} + {$node.title} + {else /} + {$node.title} + {/if} + {/volist} + + + {// ================= Variant 11:SEO 前缀 ================= } + {elseif $intVariant == 11} ++ 您当前浏览的是: + {volist name="arrBreadcrumb" id="node"} + {$node.title} + {/volist} +
+ + {// ================= Variant 12:H6 ================= } + {elseif $intVariant == 12} +
+ {volist name="arrBreadcrumb" id="node"}
+ {$node.title}
+ {/volist}
+
+
+ {// ================= Variant 14:kbd ================= }
+ {elseif $intVariant == 14}
+
+ {volist name="arrBreadcrumb" id="node"}
+ {$node.title}
+ {/volist}
+
+
+ {// ================= Variant 15:SEO 长句 ================= }
+ {elseif $intVariant == 15}
+ + 本页面为 + {volist name="arrBreadcrumb" id="node" key="i"} + {$node.title}{if $i < count($arrBreadcrumb)} 的 {/if} + {/volist} + 相关内容展示。 +
+ + {// ================= Variant 16:mark ================= } + {elseif $intVariant == 16} ++ {volist name="arrBreadcrumb" id="node"} + {$node.title} + {/volist} +
+ + {// ================= Variant 17:inline text ================= } + {elseif $intVariant == 17} + + {volist name="arrBreadcrumb" id="node" key="i"} + {$node.title}{if $i < count($arrBreadcrumb)} , {/if} + {/volist} + + + {// ================= Variant 18:footer ================= } + {elseif $intVariant == 18} + + + {// ================= Variant 19:默认 ================= } + {else} ++ {volist name="arrBreadcrumb" id="node"} + {$node.title} + {/volist} +
+ + {// ================= Variant 10:Tag ================= } + {elseif $intVariant == 10} +类型:{$arrVideo.type.type_name}
+主演:{$arrVideo.v_actor}
+地区:{$arrVideo.v_area}
+年份:{$arrVideo.v_year}
+{$arrVideo.v_des}
+{$re.v_name}
+ + {/volist} +{$arrVideo.v_year} · {$arrVideo.type.type_name} · {$arrVideo.v_area}
+主演:{$arrVideo.v_actor}
+导演:{$arrVideo.v_director}
+语言:{$arrVideo.v_lang}
+{$arrVideo.v_des}
+{$re.v_name}
+ + {/volist} +{$arrVideo.v_des}
+{$re.v_name}
+ + {/volist} +{$arrVideo.v_des}
+{$re.v_name}
+ + {/volist} +{$arrVideo.v_year} · {$arrVideo.type.type_name} · {$arrVideo.v_area}
+{$arrVideo.v_des}
+{$re.v_name}
+ + {/volist} ++ {$arrVideo.v_description} +
+ +{elseif $variant == 1} +{$arrVideo.v_description|mb_substr=0,120}
+{$arrVideo.v_description|mb_substr=0,200}
+{$arrVideo.v_description}
++ 剧情简介:{$arrVideo.v_description|mb_substr=0,150} +
+{$arrVideo.v_description}
+{$arrVideo.v_description|mb_substr=0,100}...
+ 查看详情 +{$arrVideo.v_description|mb_substr=0,180}
++ {$arrVideo.v_description|mb_substr=0,160} ++
+ {$arrVideo.v_name}({$arrVideo.v_year})剧情: + {$arrVideo.v_description|mb_substr=0,140} +
+ +{elseif $variant == 10} ++ {$arrVideo.v_description|mb_substr=0,160} +
+{$arrVideo.v_description|mb_substr=0,120}
++ {$arrVideo.v_description|mb_substr=0,100} + + {$arrVideo.v_name} 在线观看 + +
++ {$arrVideo.v_description|mb_substr=0,130} + 高清完整版内容介绍。 +
++ {$arrVideo.v_description|mb_substr=0,150} +
++ 本片支持在线播放 +
++ {$arrVideo.v_description|mb_substr=0,120} + 免费观看{$arrVideo.v_name} +
+ +{elseif $variant == 16} ++ {$arrVideo.v_name} + {$arrVideo.v_description|mb_substr=0,140} +
+{$arrVideo.v_description|mb_substr=0,180}
++ {$arrVideo.v_description} +
+{$arrVideo.v_description|mb_substr=0,160}
+| 地区 | ++ {volist name="arrVideo.v_area" id="vo" key="k"} + {$vo}{if $k < $len_area}、{/if} + {/volist} + | +
| 语言 | ++ {volist name="arrVideo.v_lang" id="vo" key="k"} + {$vo}{if $k < $len_lang}、{/if} + {/volist} + | +
{$vo.v_year}
+ {notempty name="vo.v_remarks"}{$vo.v_remarks}
{/notempty} +{$vo.v_year}
+
+
+
+导演:{$vo.v_director[0]}
+ {/if} + + {if isset($vo.v_actor[0])} +主演:{$vo.v_actor[0]}
+ {/if} +导演:{$vo.v_director[0]}
+ {/if} + {if isset($vo.v_actor[0])} +主演:{$vo.v_actor[0]}
+ {/if} +导演:{$vo.v_director[0]}
+ {/if} + + {if isset($vo.v_actor[0])} +主演:{$vo.v_actor[0]}
+ {/if} +导演:{$vo.v_director[0]}
+ {/if} + + {if isset($vo.v_actor[0])} +主演:{$vo.v_actor[0]}
+ {/if} +导演:{$vo.v_director[0]}
+ {/if} + + {if isset($vo.v_actor[0])} +主演:{$vo.v_actor[0]}
+ {/if} +导演:{$vo.v_director[0]}
+ {/if} + {if isset($vo.v_actor[0])} +主演:{$vo.v_actor[0]}
+ {/if} +导演:{$vo.v_director[0]}
+ {/if} + {if isset($vo.v_actor[0])} +主演:{$vo.v_actor[0]}
+ {/if} +导演:{$vo.v_director[0]}
+ {/if} + {if isset($vo.v_actor[0])} +主演:{$vo.v_actor[0]}
+ {/if} +导演:{$vo.v_director[0]}
+ {/if} + {if isset($vo.v_actor[0])} +主演:{$vo.v_actor[0]}
+ {/if} +导演:{$vo.v_director[0]}
+ {/if} + {if isset($vo.v_actor[0])} +主演:{$vo.v_actor[0]}
+ {/if} +导演:{$vo.v_director[0]}
+ {/if} + {if isset($vo.v_actor[0])} +主演:{$vo.v_actor[0]}
+ {/if} +{$vo.v_name}
+ + {if isset($vo.v_director[0])} +导演:{$vo.v_director[0]}
+ {/if} + {if isset($vo.v_actor[0])} +主演:{$vo.v_actor[0]}
+ {/if} +导演:{$vo.v_director[0]}
+ {/if} + {if isset($vo.v_actor[0])} +主演:{$vo.v_actor[0]}
+ {/if} ++ + {$vo.v_name} + +
+{$vo.v_name}
+ ++ {$i} + {$vo.v_name} +
++ {$title.secondary} +
+ {/notempty} +{$vo.v_name}
+ + {/volist} +全新剧集已更新
+{$vo.v_name}
+ ++ {$arrVideo.v_description|raw} +
+{$Video.v_name}
+ + + {/video:list} + ++ 主演:{$arrVideo.v_actor} +
+{$arrVideo.v_description|raw}
+{$Video.v_name}
+ + + {/video:list} ++ 主演:{$arrVideo.v_actor} +
+{$arrVideo.v_description|raw}
+{$Video.v_name}
+ + + {/video:list} +{$arrVideo.v_year} · {$arrVideo.v_category} · {$arrVideo.v_area}
+{$arrVideo.v_description|raw}
+{$Video.v_name}
+ + {/video:list} +{$arrVideo.v_description|raw}
+{$Video.v_name}
+ + + {/video:list} +| {$line} | |
|---|---|
| {$i} | +{$ep.name} | +
{$line}:
+| {$line} | + {volist name="list" id="ep" key="i"} + {$ep.name} + {/volist} + |
|---|
{$line}
++ {volist name="list" id="ep" key="i"} + {$ep.name} + {/volist} +
+ {/foreach} + + {// ================= Variant 8:Nested Table ================= } + {elseif $variant == 8} +| {$line} |
+
|
+ {volist name="list" id="ep" key="i"} + {$ep.name} + {/volist} +
++ + {$ep.name} + +
+ {/volist} ++ {volist name="list" id="ep" key="i"} + + + {$ep.name} + + + {/volist} +
+ {/foreach} + + {// ================= Variant 4:Blockquote ================= } + {elseif $variant == 4} + {foreach $arrVideo.v_play_url as $line=>$list} ++ {$line} + {volist name="list" id="ep" key="i"} ++ {/foreach} + + {// ================= Variant 5:UL + Paragraph ================= } + {elseif $variant == 5} + {foreach $arrVideo.v_play_url as $line=>$list} ++ + {$ep.name} + ++ {/volist} +
+ + {$ep.name} + +
++ 本线路提供以下资源: + {volist name="list" id="ep" key="i"} + + {$ep.name} + + {/volist} +
++ 第 {$i} 集: + + {$ep.name} + +
+ {/volist} + {/foreach} + + {// ================= Variant 9:Minimal Reading ================= } + {elseif $variant == 9} + {foreach $arrVideo.v_play_url as $line=>$list} +热度上升中 → 点击查看
+{$i}. {$vo.v_name}
+ + {/volist} +热度提升中
+{$vo.v_name}
+ +搜索结果
+关键词
+为你呈现匹配内容
+搜索结果页
+相关搜索结果
+结果列表
+结果展示
+按相关度排序
+即时匹配
+搜索结果
+结果页
+匹配内容如下
+搜索结果页
+可换词
+为你精选
+搜索聚合
+media
+相关内容+
与你查询相关的内容合集
+为你推荐相关内容
+{$Request.get.keyword}结果列表
+按热度聚合的搜索内容
+以下是匹配条目
+{$Request.get.keyword}相关内容已按主题整理
+关键词:{$Request.get.keyword}
+你可能还想找:
+{$Request.get.keyword}”结果已按相关度排序
+站内搜索
+可能拼写不同
+聚合页
+| 关键词 | +{$Request.get.keyword} | +
{$Request.get.keyword}内容合集
+列表页 · 可索引
+结果列表
+ +搜索结果页
+站内搜索结果
+搜索结果
+搜索结果列表页
+结果页(SEO)
+搜索结果
+索引列表页
+站内匹配内容
+{$Request.get.keyword}
+{$Request.get.keyword}
+结果页
+聚合结果页
+可抓取
+搜索结果目录
+结果页
+搜索结果索引
+ 站内 +Search result page
+结果列表页
+索引结构稳定
+站内搜索
+list
+搜索结果
+indexable
+相关条目如下
+Search
+可尝试同义词
+搜索结果列表
+关键词:{$Request.get.keyword}
+为你整理相关内容
+结果页
+ ++ {volist name="arrSeoWords" id="it"} + {$it.text} + {/volist} +
+ + {// ================= Variant 3:span ================= } + {elseif $intVariant == 3} + + {volist name="arrSeoWords" id="it"} + {$it.text} + {/volist} + + + {// ================= Variant 4:strong ================= } + {elseif $intVariant == 4} ++ {volist name="arrSeoWords" id="it"} + {$it.text} + {/volist} +
+ + {// ================= Variant 5:em ================= } + {elseif $intVariant == 5} ++ {volist name="arrSeoWords" id="it"} + {$it.text} + {/volist} +
+ + {// ================= Variant 6:text sentence ================= } + {elseif $intVariant == 6} ++ 相关关键词: + {volist name="arrSeoWords" id="it"} + {$it.text}, + {/volist} +
+ + {// ================= Variant 7:address ================= } + {elseif $intVariant == 7} + + {volist name="arrSeoWords" id="it"} + {$it.text} + {/volist} + + + {// ================= Variant 8:blockquote ================= } + {elseif $intVariant == 8} ++ {volist name="arrSeoWords" id="it"} + {$it.text} + {/volist} ++ + {// ================= Variant 9:small ================= } + {elseif $intVariant == 9} + + {volist name="arrSeoWords" id="it"} + {$it.text} + {/volist} + + + {// ================= Variant 10:mark ================= } + {elseif $intVariant == 10} +
+ {volist name="arrSeoWords" id="it"} + {$it.text} + {/volist} +
+ + {// ================= Variant 11:code ================= } + {elseif $intVariant == 11} +
+ {volist name="arrSeoWords" id="it"}
+ {$it.text}
+ {/volist}
+
+
+ {// ================= Variant 12:kbd ================= }
+ {elseif $intVariant == 12}
+
+ {volist name="arrSeoWords" id="it"}
+ {$it.text}
+ {/volist}
+
+
+ {// ================= Variant 13:h6 ================= }
+ {elseif $intVariant == 13}
+ {$it.text}
+ {/volist} ++ {volist name="arrSeoWords" id="it"}{$it.text},{/volist} +
+ + {// ================= Variant 1:多段落 ================= } + {elseif $intVariant == 1} + {volist name="arrSeoWords" id="it"} +{$it.text}
+ {/volist} + + {// ================= Variant 2:说明文本 ================= } + {elseif $intVariant == 2} ++ 以下内容与 + {volist name="arrSeoWords" id="it"}{$it.text}、{/volist} + 相关。 +
+ + {// ================= Variant 3:引用说明 ================= } + {elseif $intVariant == 3} ++ {volist name="arrSeoWords" id="it"}{$it.text} {/volist} ++ + {// ================= Variant 4:混合 strong ================= } + {elseif $intVariant == 4} +
+ {volist name="arrSeoWords" id="it"} + {$it.text}, + {/volist} +
+ + {// ================= Variant 5:混合 em ================= } + {elseif $intVariant == 5} ++ {volist name="arrSeoWords" id="it"} + {$it.text}, + {/volist} +
+ + {// ================= Variant 6:文章段 ================= } + {elseif $intVariant == 6} ++ {volist name="arrSeoWords" id="it"}{$it.text} {/volist} +
+{$it.text}
+ {/volist} ++ 相关搜索: + {volist name="arrSeoWords" id="it"}{$it.text},{/volist} +
+ + {// ================= Variant 11:正文后缀 ================= } + {elseif $intVariant == 11} ++ {volist name="arrSeoWords" id="it"}{$it.text},{/volist} + 等内容可供参考。 +
+ + {// ================= Variant 12:段落 + 链接 ================= } + {elseif $intVariant == 12} ++ {volist name="arrSeoWords" id="it"} + {$it.text}, + {/volist} +
+ + {// ================= Variant 13:h6 + 段落 ================= } + {elseif $intVariant == 13} ++ {volist name="arrSeoWords" id="it"}{$it.text} {/volist} +
+ + {// ================= Variant 14:多句合并 ================= } + {elseif $intVariant == 14} ++ {volist name="arrSeoWords" id="it"}{$it.text}。{/volist} +
+ + {// ================= Variant 15:解释型 ================= } + {elseif $intVariant == 15} ++ 本页涉及 + {volist name="arrSeoWords" id="it"}{$it.text}、{/volist} + 等关键词。 +
+ + {// ================= Variant 16:aside ================= } + {elseif $intVariant == 16} + + + {// ================= Variant 17:mark 混排 ================= } + {elseif $intVariant == 17} ++ {volist name="arrSeoWords" id="it"}{$it.text} {/volist} +
+ + {// ================= Variant 18:长文本 ================= } + {elseif $intVariant == 18} ++ {volist name="arrSeoWords" id="it"}{$it.text} {/volist} + 为您提供更多参考信息。 +
+ + {// ================= Variant 19:默认 ================= } + {else} +以下内容为您推荐:
+ {volist name="arrSeoWords" id="it"}{$it.text}{/volist} +{$it.text}
{/volist} +影片热度上涨,立即前往观看
+
{volist name="arrVideoPiaofang" id="Video"}
-
{volist name="arrVideoPiaofang" id="Video"}
-
{$Video.v_remarks}
diff --git a/code/app/home/view/videoGpt1/public/video-list-shu.html b/code/app/home/view/videoGpt1/public/video-list-shu.html
index 574619f..c47684c 100644
--- a/code/app/home/view/videoGpt1/public/video-list-shu.html
+++ b/code/app/home/view/videoGpt1/public/video-list-shu.html
@@ -5,7 +5,7 @@
{$arrGrm[$key+[start]]|raw}
{/if}
- {php}$desc = \app\common\helper\TextSpin::spin($info['intro'], $style['theme_id']);{/php} - {$desc} -
- - 开始播放 -
+ {php}print_r($resData['p_data']);{/php}
+
+
\ No newline at end of file