// Isolated UI fixture using the actual built prefab, scripts, engine and bundle assets. // Build web-mobile first; optionally set CLOUD_RISE_BUILD, then node tools/preview-cloud-rise.cjs [port]. const http=require('node:http'),fs=require('node:fs'),path=require('node:path'); const root=path.resolve(__dirname,'..'); const built=process.env.CLOUD_RISE_BUILD||path.join(root,'build/cloud-rise-prefab-verification/web-mobile'); const port=Number(process.argv[2]||8765); const engine=fs.readdirSync(built).find(n=>/^cocos2d-js.*\.js$/.test(n)); const html = ` cloudRise · Cocos UI preview
本地测试
`; const boot = ` window.previewErrors=[];window.addEventListener('error',e=>previewErrors.push(e.message)); window.addEventListener('unhandledrejection',e=>previewErrors.push(String(e.reason))); let listeners=[],data,ack=false,view; const profile={username:'小猫玩家',useravatar:'',useravatarIcon:'icon_9'}; const makeStage=n=>({stage:n,target:[5,7,9][n-1],pool:[10000,15000,20000][n-1],success_num:0,survivors:100,status:'playing',reward:0,opponents:Array.from({length:99},(_,i)=>({...profile,username:'玩家'+(i+1),alive:true}))}); const service={error:'',data:()=>data,run:()=>data.run,stage:()=>data.run&&data.run.stages.at(-1),now:()=>Date.now(),visible:()=>true,subscribe:fn=>(listeners.push(fn),()=>listeners=listeners.filter(x=>x!==fn)),resultKey:()=>data.run&&data.run.id+':'+data.run.stage+':'+data.run.status,unseenResult:()=>!ack&&data.run&&['waiting','failed','completed','expired'].includes(data.run.status),acknowledgeResult:()=>ack=true,sync:async()=>true, startStage:async()=>{const n=data.run&&data.run.status==='waiting'?data.run.stage+1:1;data.available=false;if(!data.run)data.run={id:'preview',stage:0,status:'playing',expiresAt:Date.now()+86400000,stages:[]};data.run.stage=n;data.run.status='playing';data.run.stages.push(makeStage(n));ack=false;listeners.forEach(fn=>fn());return true;}}; window.previewService=service; function fixture(name){ack=false;data={available:true,pools:[10000,15000,20000],period:{enabled:true,startsAt:Date.now()-1000,endsAt:Date.now()+86400000},run:null};if(name!=='报名'){const n=name.includes('stage3')?3:name.includes('stage2')?2:1;data.available=false;data.run={id:'preview-'+name,stage:n,status:'playing',expiresAt:Date.now()+86395000,stages:[makeStage(n)]};const s=service.stage();s.success_num=2;s.survivors=47;s.opponents.forEach((o,i)=>o.alive=i<46);if(name==='stage2待开始'||name==='成功'){s.stage=1;s.target=5;s.success_num=5;s.status='won';s.rewardSaved=true;s.survivors=3;s.reward=3340;data.run.stage=1;data.run.status='waiting';s.opponents.forEach((o,i)=>o.alive=i<2);}if(name==='失败'){s.status='lost';data.run.status='failed';}if(name==='超时'){s.status='expired';data.run.status='expired';data.run.expiresAt=Date.now()-1000;}}} fixture('报名'); cc.assetManager.init({}); const loadBundle=name=>new Promise((resolve,reject)=>cc.assetManager.loadBundle(name,(error,bundle)=>error?reject(error):resolve(bundle))); loadBundle('internal').then(()=>cc.game.run({id:'GameCanvas',debugMode:1,frameRate:30,renderMode:0,groupList:['default'],collisionMatrix:[[true]]},async()=>{ try { cc.view.enableRetina(true);cc.view.resizeWithBrowserSize(true);cc.view.setDesignResolutionSize(1080,2200,cc.ResolutionPolicy.SHOW_ALL); cc.fx={GameConfig:{GM_INFO:{...profile,uid:'fixture',username:'我',coin:100,otherLevel:0}},GameTool:{getActionName:()=> 'action11'},StorageMessage:{getStorage:()=>null,setStorage:()=>{}}}; await loadBundle('resources');await loadBundle('main');const bundle=await loadBundle('cloud_rise_home'); const runtime=window.__require('CloudRiseRuntime').default;Object.assign(runtime,service); const prefab=await new Promise((resolve,reject)=>bundle.load('prefab/CloudRise',cc.Prefab,(error,asset)=>error?reject(error):resolve(asset))); const scene=new cc.Scene(),canvas=new cc.Node('Canvas');scene.addChild(canvas);const comp=canvas.addComponent(cc.Canvas);comp.designResolution=cc.size(1080,2200);comp.fitHeight=true;comp.fitWidth=true;cc.director.runSceneImmediate(scene); const backdrop=new cc.Node('PreviewHome');canvas.addChild(backdrop);const bg=backdrop.addComponent(cc.Graphics);bg.fillColor=cc.color(240,224,188);bg.rect(-540,-1100,1080,2200);bg.fill(); for(const [text,x,y] of [['首页 · 本地预览',0,1010],['体力 5',-290,880],['金币 563',290,880],['关卡地图',0,-920]]){const n=new cc.Node();backdrop.addChild(n);n.setPosition(x,y);const l=n.addComponent(cc.Label);l.string=text;l.fontSize=44;n.color=cc.color(94,68,42);} let panelNode=null; view={close(){if(panelNode){panelNode.destroy();panelNode=null;}},async open(){panelNode=cc.instantiate(prefab);canvas.addChild(panelNode);const panel=panelNode.getComponent('CloudRisePanel');const mode=service.unseenResult()?'result':service.run()&&service.run().status==='playing'?'progress':'overview';await panel.preparePage(mode);panel.initialize(mode,false,()=>view.close(),()=>console.log('进入主线'));}}; await view.open();window.previewReady=true; document.getElementById('show').onclick=async()=>{view.close();fixture(document.getElementById('state').value);listeners.forEach(fn=>fn());await view.open();}; document.getElementById('win').onclick=()=>{const s=service.stage();if(!s||s.status!=='playing')return;s.success_num++;s.survivors=Math.max(1,s.survivors-8);if(s.success_num>=s.target){s.status='won';s.rewardSaved=true;s.reward=Math.ceil(Math.ceil(s.pool/s.survivors)/10)*10;data.run.status=data.run.stage===3?'completed':'waiting';}listeners.forEach(fn=>fn());}; }catch(error){console.error('Prefab preview failed',error);} })).catch(console.error); `; http.createServer((req,res)=>{ const url=new URL(req.url,'http://localhost').pathname; if(url==='/'){res.setHeader('Content-Type','text/html; charset=utf-8');return res.end(html);} if(url==='/preview.js'){res.setHeader('Content-Type','text/javascript; charset=utf-8');return res.end(boot);} let relative=url==='/engine.js'?engine:decodeURIComponent(url).replace(/^\//,''); const match=relative.match(/^assets\/([^/]+)\/(config\.json|index\.js)$/); if(match){const dir=path.join(built,'assets',match[1]);if(fs.existsSync(dir)){const name=fs.readdirSync(dir).find(n=>match[2]==='config.json'?/^config.*\.json$/.test(n):/^index.*\.js$/.test(n));if(name)relative='assets/'+match[1]+'/'+name;}} const file=path.resolve(built,relative); if(!file.startsWith(path.resolve(built)+path.sep)||!fs.existsSync(file)||!fs.statSync(file).isFile()){res.writeHead(404);return res.end();} res.setHeader('Content-Type',file.endsWith('.js')?'text/javascript':file.endsWith('.json')?'application/json':file.endsWith('.png')?'image/png':'application/octet-stream');fs.createReadStream(file).pipe(res); }).listen(port,'127.0.0.1',()=>console.log('cloudRise prefab preview http://127.0.0.1:'+port));