[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:
@@ -0,0 +1,52 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createContext } from '../src/server/context';
|
||||
import { buildPage } from '../src/server/pages';
|
||||
import { apiData } from '../src/server/services/data';
|
||||
import { applyBrandCopy, BRAND_NAME, replaceBrandText } from '../src/server/services/brand';
|
||||
import { model } from '../src/server/services/model';
|
||||
import { buildLLMSTXT, enrichSEO, normalizeSEO, resolvePlayPageSEO, toNextMetadata, verificationCodes } from '../src/server/seo';
|
||||
|
||||
const context=(path='/')=>createContext(new Request('http://internal:3000'+path));
|
||||
|
||||
test('brand replacement changes the full display phrase while preserving unrelated names and operational values',()=>{
|
||||
const value:Record<string,any>=model({title:'黄果恋人 - 黄果短剧',name:'黄果短剧',description:'黄果短剧精彩推荐',keywords:['黄果短剧','黄果恋人'],canonicalURL:'https://canonical.drama.test/黄果短剧/',url:'/黄果短剧/',slug:'黄果短剧',token:'黄果短剧-token',reportSdkConfig:'黄果短剧-signed',email:'support@huangguo.com',verification:'黄果短剧-verify',text:'用户说黄果短剧',announcements:[{content:'欢迎访问黄果短剧'}]});
|
||||
const result=applyBrandCopy(value);
|
||||
assert.equal(result,value);assert.equal(result.title,'黄果恋人 - 花魁短剧');assert.equal(result.Name,BRAND_NAME);assert.equal(result.DisplayName,BRAND_NAME);assert.deepEqual(result.keywords,['花魁短剧','黄果恋人']);
|
||||
assert.equal(result.canonicalURL,'https://canonical.drama.test/黄果短剧/');assert.equal(result.url,'/黄果短剧/');assert.equal(result.slug,'黄果短剧');assert.equal(result.token,'黄果短剧-token');assert.equal(result.reportSdkConfig,'黄果短剧-signed');assert.equal(result.email,'support@huangguo.com');assert.equal(result.verification,'黄果短剧-verify');assert.equal(result.text,'用户说黄果短剧');assert.equal(result.announcements[0].content,'欢迎访问花魁短剧');
|
||||
assert.equal(replaceBrandText('黄果、黄果恋人、黄果短剧、黄果短剧'),'黄果、黄果恋人、花魁短剧、花魁短剧');
|
||||
});
|
||||
|
||||
test('upstream TDK aliases, navigation metadata and llms expose the new brand without changing canonical settings',()=>{
|
||||
const canonical='https://canonical.drama.test/黄果短剧/?source=original';
|
||||
const seo=normalizeSEO(JSON.stringify({WebTitle:' 黄果短剧首页 ',WebDescription:'黄果短剧介绍',WebKeyworld:'黄果短剧,花魁短剧,黄果恋人',CanonicalURL:canonical,WebSiteName:'黄果短剧'}));
|
||||
assert.equal(seo.title,'花魁短剧首页');assert.equal(seo.description,'花魁短剧介绍');assert.equal(seo.webSiteName,BRAND_NAME);assert.deepEqual(seo.keywords,['花魁短剧','黄果恋人']);assert.equal(seo.canonicalUrl,canonical);
|
||||
const request=context('/list/');
|
||||
const codes=verificationCodes();
|
||||
const data=enrichSEO(request,{appConfig:{foreverUrlAddr:'https://canonical.drama.test/permanent?keep=1',seoInfo:{webSiteName:'黄果短剧',description:'黄果短剧全站描述'}},categoryList:[{name:'短剧',categoryType:1,urlValue:'/list',seoInfo:{title:'黄果短剧分类',description:'黄果短剧分类介绍',keyworld:['黄果短剧','都市']}}],pagination:{Page:2,Visible:true,HasPrev:true,PrevURL:'/list/'}});
|
||||
assert.equal(data.metaTitle,'花魁短剧分类 - 第2页');assert.equal(data.metaDescription,'花魁短剧分类介绍 - 第2页');assert.equal(data.metaKeywords,'花魁短剧,都市,第2页');assert.equal(data.siteName,BRAND_NAME);
|
||||
assert.equal(data.siteURL,'https://canonical.drama.test');assert.equal(data.canonicalURL,'https://canonical.drama.test/dramas/');assert.equal(data.paginationPrevURL,'https://canonical.drama.test/dramas/');assert.equal(data.robots,'index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1');assert.deepEqual(data.seoVerify,codes);
|
||||
const graph=JSON.parse(data.jsonLD)['@graph'];assert.equal(graph.find((item:any)=>item['@type']==='Organization').name,BRAND_NAME);assert.equal(graph.find((item:any)=>item['@type']==='WebSite').name,BRAND_NAME);
|
||||
const metadata=toNextMetadata(data);assert.equal(metadata.openGraph?.siteName,BRAND_NAME);assert.equal(metadata.alternates?.canonical,data.canonicalURL);
|
||||
const llms=buildLLMSTXT('https://canonical.drama.test',{seoInfo:{webSiteName:'黄果短剧',description:'黄果短剧介绍'}});assert.match(llms,/^# 花魁短剧/);assert.match(llms,/> 花魁短剧介绍/);assert.match(llms,/https:\/\/canonical\.drama\.test\/sitemap\.xml/);
|
||||
const play=resolvePlayPageSEO({}, {playName:'黄果恋人',desc:'黄果恋人的故事,由黄果短剧提供'},2);assert.equal(play.title,'黄果恋人 - 第二集');assert.equal(play.description,'黄果恋人的故事,由花魁短剧提供');
|
||||
});
|
||||
|
||||
test('page injection and cached upstream configuration cannot restore the former brand',async()=>{
|
||||
const originalFetch=globalThis.fetch,originalUpstream=process.env.UPSTREAM_API_URL;
|
||||
process.env.UPSTREAM_API_URL='http://brand-contract.test';const calls=new Map<string,number>();
|
||||
globalThis.fetch=async(input)=>{
|
||||
const path=new URL(String(input)).pathname;calls.set(path,(calls.get(path)||0)+1);let data:unknown={list:[],totalCount:0};
|
||||
if(path==='/api/app/config/info')data={config:{foreverUrlAddr:'https://canonical.drama.test',gmailAddr:'support@huangguo.com',brandName:'黄果短剧'},seoInfo:{title:'黄果短剧首页',description:'黄果短剧介绍',webSiteName:'黄果短剧',keywords:['黄果短剧']},seoNavInfo:{dhTitle:'黄果短剧导航',dhDescription:'黄果短剧最新线路',dhSiteName:'黄果短剧'},reportSdkConfig:'黄果短剧-signed-sdk'};
|
||||
if(path==='/api/app/translation/category/list')data={categoryList:[{name:'黄果短剧首页',urlValue:'/',categoryType:1,seoInfo:{title:'黄果短剧首页',description:'黄果短剧介绍'}}]};
|
||||
if(path==='/api/app/playlet/home')data={seoInfo:{title:'黄果短剧首页'},announcements:[{content:'欢迎来到黄果短剧'}],homeConfigData:[{name:'黄果短剧精选',type:1,status:true}],homeBannerData:[{id:1,playName:'黄果恋人'}]};
|
||||
return Response.json({code:200,data});
|
||||
};
|
||||
try{
|
||||
const page=await buildPage(context('/'));const config=page.data.appConfig;
|
||||
assert.equal(page.data.title,'花魁短剧首页');assert.equal(config.config.brandName,BRAND_NAME);assert.equal(config.seoInfo.webSiteName,BRAND_NAME);assert.equal(config.seoNavInfo.dhTitle,'花魁短剧导航');assert.equal(page.data.categoryList[0].name,'花魁短剧首页');assert.equal(page.data.announcements[0].content,'欢迎来到花魁短剧');assert.equal(page.data.homeConfigPlaylets[0].name,'花魁短剧精选');assert.equal(page.data.homeBannerData[0].playName,'黄果恋人');assert.equal(config.gmailAddr,'support@huangguo.com');assert.equal(config.reportSdkConfig,'黄果短剧-signed-sdk');
|
||||
const cached=await apiData(context('/'),'/api/app/config/info',{}, {public:true,ttl:60000});assert.equal(cached.seoInfo.webSiteName,BRAND_NAME);assert.equal(cached.config.brandName,BRAND_NAME);assert.equal(calls.get('/api/app/config/info'),1);
|
||||
const rendered=enrichSEO(context('/'),page.data);assert.equal(rendered.siteName,BRAND_NAME);assert.equal(rendered.canonicalURL,'https://canonical.drama.test/');assert.equal(rendered.metaTitle,'花魁短剧首页');
|
||||
const help=await buildPage(context('/help/'));assert.equal(help.data.faqItems[0].Question,'花魁短剧网页版可以看全集吗?');
|
||||
}finally{globalThis.fetch=originalFetch;if(originalUpstream===undefined)delete process.env.UPSTREAM_API_URL;else process.env.UPSTREAM_API_URL=originalUpstream;}
|
||||
});
|
||||
Reference in New Issue
Block a user