[Ai] feat: 花魁短剧 Next.js 站点初始提交

- 原生 Next.js App Router + 严格 TypeScript 实现花魁短剧 PC/H5 页面,按 src/features/{catalog,content,playback} 组织,保留完整 SEO(metadata、JSON-LD、canonical、robots、llms、sitemap、IndexNow)。
- 主域名通过 SITE_URL=https://huakuidj.com 强制覆盖后端 foreverUrlAddr(config/*.env、src/server/config.ts、src/server/seo/shared.ts、src/server/services/shared.ts),支持邮箱改为 support@huakuidj.com。
- 移除登录与账户体系:删除 /account 全部页面、登录/注册/充值弹窗、收藏点赞、评论、金币解锁及对应 BFF 接口;旧账户地址 GET/HEAD 统一 301 到首页(src/lib/routes.ts REMOVED_ACCOUNT_PATHS),保留游客会话、反馈、图片上传、观看上报与搜索联想。
- 播放页:评论位改为同类推荐 + 热播榜单,锁定集改为 App 观看遮罩与底部弹层,新增简介标签、分集图例与独立视觉皮肤(src/features/playback、src/styles/pages/playback.css)。
- 卡片:短剧/视频卡状态与时长移至信息行胶囊标签,封面角标移至左上角(src/components/catalog/Cards.tsx、src/styles/foundations/base.css)。
- 同步更新单元测试、浏览器冒烟脚本、路由清单与 README。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
谢宇宁
2026-10-16 11:33:31 +07:00
co-authored by Claude Opus 5.5
commit d2ddb1c321
423 changed files with 52152 additions and 0 deletions
+71
View File
@@ -0,0 +1,71 @@
import { readFile, writeFile, mkdir } from 'node:fs/promises';
import { resolve, dirname } from 'node:path';
import { pageRoutes } from '../src/server/pages/routes';
import { ROUTE_MIGRATIONS, toPublicPath } from '../src/lib/routes';
const labels: Record<string, string> = {
Index: '首页', SearchMobile: '移动端搜索', Search: '搜索结果',
About: '关于我们', Help: '帮助中心', Terms: '服务条款', Privacy: '隐私政策',
Compliance2257: '2257 声明', DMCA: '版权声明', DownloadApp: '下载 App',
Nav: '官方导航', HomeRoad: '回家的路', List: '短剧列表', MoreList: '短剧推荐栏目',
MoreSeriesList: '剧集推荐栏目', Series: '剧集列表', Topic: '专题列表',
TopicDetail: '专题详情', Rank: '排行榜', Tags: '标签详情',
RecentlyAdded: '最新更新', PopularRecommend: '热门推荐', Play: '短剧播放',
SeriesPlay: '剧集播放', Feedback: '意见反馈',
};
type CatalogRoute = { id: string; label: string; path: string; legacyPaths: string[]; selectable: boolean };
const routes = new Map<string, CatalogRoute>();
for (const [legacy, handler] of pageRoutes) {
if (!labels[handler]) throw new Error('Missing page label: ' + handler);
const path = toPublicPath(legacy);
let entry = routes.get(path);
if (!entry) {
entry = { id: handler + ':' + path, label: labels[handler], path, legacyPaths: [], selectable: !path.includes(':') };
routes.set(path, entry);
}
if (!entry.legacyPaths.includes(legacy)) entry.legacyPaths.push(legacy);
}
const catalog = JSON.stringify({ version: 1, routes: [...routes.values()] }, null, 2) + '\n';
const begin = '<!-- BEGIN PAGE_ROUTE_CATALOG -->';
const end = '<!-- END PAGE_ROUTE_CATALOG -->';
const inventory = [
begin,
'以下清单由 `npm run routes:export` 从实际路由注册生成,包含全部页面、筛选和分页模式。含 `:参数` 的路径需要填写真实值;后台导航下拉只展示可直接访问的固定入口,详情页和外链可手动输入。',
'',
'| 页面 | 原路径 | 当前路径 |',
'| --- | --- | --- |',
...[...routes.values()].map(item => `| ${item.label} | ${item.legacyPaths.map(path => '`' + path + '`').join('、')} | \`${item.path}\` |`),
'',
end,
].join('\n');
const root = resolve(import.meta.dirname, '..');
const args = process.argv.slice(2);
const check = args.includes('--check');
const adminIndex = args.indexOf('--admin');
for (let i = 0; i < args.length; i++) {
if (args[i] === '--admin') { i++; continue; }
if (args[i] !== '--check') throw new Error('Usage: routes:export [--check] [--admin <admin-repository>]');
}
if (adminIndex >= 0 && (!args[adminIndex + 1] || args[adminIndex + 1].startsWith('--'))) throw new Error('--admin requires the admin repository path');
const readmePath = resolve(root, 'README.md');
const readme = await readFile(readmePath, 'utf8');
const start = readme.indexOf(begin), finish = readme.indexOf(end);
if (start < 0 || finish < start) throw new Error('README route inventory markers are missing');
const updatedReadme = readme.slice(0, start) + inventory + readme.slice(finish + end.length);
const outputs: [string, string][] = [[resolve(root, 'src/lib/page-route-catalog.json'), catalog], [readmePath, updatedReadme]];
if (adminIndex >= 0) {
const adminRoot = resolve(args[adminIndex + 1]!);
const pkg = JSON.parse(await readFile(resolve(adminRoot, 'package.json'), 'utf8'));
if (pkg.name !== 'hkdj-web-admin') throw new Error('--admin must target the Huakui web admin repository');
outputs.push([resolve(adminRoot, 'src/constants/hkdj-web-routes.json'), catalog]);
}
for (const [path, content] of outputs) {
if (check) {
if (await readFile(path, 'utf8') !== content) throw new Error('Route catalog is stale: ' + path);
} else {
await mkdir(dirname(path), { recursive: true });
await writeFile(path, content);
}
}
console.log(`${check ? 'Verified' : 'Exported'} ${ROUTE_MIGRATIONS.length} migrations, ${routes.size} page patterns, ${[...routes.values()].filter(item => item.selectable).length} navigation choices.`);