From 1cb11bc1dde721c46b8defb8aff6aced04dde23c Mon Sep 17 00:00:00 2001 From: kdletters Date: Mon, 1 Jun 2026 16:45:39 +0000 Subject: [PATCH] fix: refine profile shortcuts and puzzle next button --- ...玩法创作】平å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md | 4 +- ...项目基线】当å‰äº§å“与工程约æŸ-2026-05-15.md | 2 +- .../PuzzleRuntimeShell.test.tsx | 51 ++++++++++---- .../puzzle-runtime/PuzzleRuntimeShell.tsx | 69 +++++++++++++++---- .../RpgEntryHomeView.recharge.test.tsx | 15 ++++ src/components/rpg-entry/RpgEntryHomeView.tsx | 4 +- 6 files changed, 113 insertions(+), 32 deletions(-) diff --git a/docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md b/docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md index 4c9d9197..d367bf19 100644 --- a/docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md +++ b/docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md @@ -52,7 +52,7 @@ 8. ç§æœ‰ generated 图片必须通过 `ResolvedAssetImage` / `/api/assets/read-url` æ¢ç­¾è¯»å–。 9. æ•²æœ¨é±¼ä½œå“æž¶è¯»å–当å‰ç”¨æˆ·ä½œå“列表时走 `GET /api/creation/wooden-fish/works`ï¼›å‘布æˆåŠŸåŽå¹³å°å£³å¿…é¡»åŒæ—¶åˆ·æ–°ä½œå“架与公开广场,é¿å…作å“刚å‘布时ä»åœç•™åœ¨æ—§åˆ—表。 -å‘现 Tabã€åˆ›ä½œ Tab 与è‰ç¨¿ Tab çš„é¡µé¢æ ¹å†…容区ä¸å†å¥— `platform-page-stage` 外层全局å¡ç‰‡å£³ï¼Œè®©åˆ—表ã€ç­›é€‰å’ŒçŽ©æ³•å¡èŽ·å¾—æ›´å®½çš„æ¨ªå‘空间;推èé¡µå’Œæˆ‘çš„é¡µä»æŒ‰å„自页é¢è®¾è®¡ä¿ç•™åŽŸæœ‰å…¨å±€å¡ç‰‡å£å¾„。移动端“我的â€é¡µä»æŒ‰é¡¶éƒ¨å¤´åƒ / 昵称 / é™¶æ³¥å·ã€ä¼šå‘˜æ¨ªå¹…ã€ä¸‰å¼ ç»Ÿè®¡å¡ã€æ¯æ—¥ä»»åŠ¡ã€äº”项常用功能宫格ã€è®¾ç½®å…¥å£å’Œæ³•律信æ¯ç»„织,ä¸ä¿ç•™æ—§çš„底部“填邀请ç â€æ¬¡çº§å…¥å£ï¼›æ¯æ—¥ä»»åŠ¡å¡å¿…é¡»è¯»å– `/api/profile/tasks` 的当å‰ä»»åŠ¡æ‘˜è¦å¹¶åœ¨é¢†å–åŽåŒæ­¥åˆ·æ–°å¡ç‰‡è¿›åº¦ã€‚å­—å·å¿…须维æŒå¹³å°æ™®é€š UI æ¡£ä½ï¼Œä¸èƒ½å› ä¸ºçª„å±æŠŠå¡ç‰‡æ ‡é¢˜ã€åŠŸèƒ½ label æˆ–æ³•å¾‹ä¿¡æ¯æ’‘æˆå±•示级字å·ï¼›æœ€åŽä¸€å±å†…容必须能在底部 dock 上方完整滚动露出,ä¸å¾—è¢«å›ºå®šåº•éƒ¨å¯¼èˆªé®æŒ¡ã€‚ +å‘现 Tabã€åˆ›ä½œ Tab 与è‰ç¨¿ Tab çš„é¡µé¢æ ¹å†…容区ä¸å†å¥— `platform-page-stage` 外层全局å¡ç‰‡å£³ï¼Œè®©åˆ—表ã€ç­›é€‰å’ŒçŽ©æ³•å¡èŽ·å¾—æ›´å®½çš„æ¨ªå‘空间;推èé¡µå’Œæˆ‘çš„é¡µä»æŒ‰å„自页é¢è®¾è®¡ä¿ç•™åŽŸæœ‰å…¨å±€å¡ç‰‡å£å¾„。移动端“我的â€é¡µä»æŒ‰é¡¶éƒ¨å¤´åƒ / 昵称 / é™¶æ³¥å·ã€ä¼šå‘˜æ¨ªå¹…ã€ä¸‰å¼ ç»Ÿè®¡å¡ã€æ¯æ—¥ä»»åŠ¡ã€äº”项常用功能宫格ã€è®¾ç½®å…¥å£å’Œæ³•律信æ¯ç»„织,ä¸ä¿ç•™æ—§çš„底部“填邀请ç â€æ¬¡çº§å…¥å£ï¼›å¸¸ç”¨åŠŸèƒ½å½“å‰åªå±•ç¤ºå››é¡¹å¸¸é©»å…¥å£æ—¶å¿…须按四列铺满整行,ä¸ä¿ç•™äº”列网格导致左对é½ç©ºä½ï¼›æ¯æ—¥ä»»åŠ¡å¡å¿…é¡»è¯»å– `/api/profile/tasks` 的当å‰ä»»åŠ¡æ‘˜è¦å¹¶åœ¨é¢†å–åŽåŒæ­¥åˆ·æ–°å¡ç‰‡è¿›åº¦ã€‚å­—å·å¿…须维æŒå¹³å°æ™®é€š UI æ¡£ä½ï¼Œä¸èƒ½å› ä¸ºçª„å±æŠŠå¡ç‰‡æ ‡é¢˜ã€åŠŸèƒ½ label æˆ–æ³•å¾‹ä¿¡æ¯æ’‘æˆå±•示级字å·ï¼›æœ€åŽä¸€å±å†…容必须能在底部 dock 上方完整滚动露出,ä¸å¾—è¢«å›ºå®šåº•éƒ¨å¯¼èˆªé®æŒ¡ã€‚ ## RPG / 自定义世界 @@ -107,7 +107,7 @@ RPG / 拼图等è¿è¡Œæ€å­˜æ¡£ä»ä»¥ `/api/profile/save-archives` çš„åŽç«¯åˆ— - 结果页å•关测试åªèƒ½æŠŠå®Œæ•´è‰ç¨¿æŒä¹…化,并通过 `levelId` 指定è¿è¡Œæ€èµ·å§‹å…³å¡ï¼›ä¸å¾—把å•关快照作为整份è‰ç¨¿è°ƒç”¨ `updatePuzzleWork`,å¦åˆ™ source session å’Œä½œå“ profile çš„ `levels` 会被覆盖æˆå•关,退出é‡è¿›åŽå…¶å®ƒå…³å¡ä¼šä¸¢å¤±ã€‚ - 拼图试玩和正å¼è¿è¡Œæ€åˆ·æ–°æ¢å¤ä¸å¤ç”¨åˆ›ä½œç§æœ‰ query。进入 `/runtime/puzzle` 时必须写入 `runtimeProfileId`ã€è‰ç¨¿ `runtimeSessionId`ã€å¯é€‰ `runtimeLevelId`ã€å…¬å¼€ä½œå“ `work` å’Œ `mode=draft|published`;进入è¿è¡Œæ€çš„导航顺åºå¿…须先切到 `/runtime/puzzle`,å†å†™è¿™äº› runtime query,é¿å…被阶段导航清掉åŽåˆ·æ–°åœåœ¨â€œæ­£åœ¨è¿›å…¥æ‹¼å›¾å…³å¡â€ã€‚ - 结果页生æˆå…³å¡å›¾æ—¶è‹¥å…³å¡å为空,å‰ç«¯å¿…须传 `shouldAutoNameLevel=true`,åŽç«¯å¤ç”¨é¦–关命åå¥‘çº¦å…ˆæŒ‰ç”»é¢æè¿°ç”Ÿæˆå…³å¡å,å†åœ¨å›¾ç‰‡ç”ŸæˆåŽç”¨è§†è§‰å‘½å结果精修,并把生æˆåå’Œ UI 背景æç¤ºè¯éšæœ¬æ¬¡å…³å¡å¿«ç…§å†™å›žã€‚ -- 拼图è¿è¡Œæ€èƒŒæ™¯ä¼˜å…ˆè¯»å–当å‰å…³å¡ `levelBackgroundImageSrc/levelBackgroundImageObjectKey`ï¼Œæ—§æ•°æ®æ‰å…¼å®¹ `uiBackgroundImageSrc/uiBackgroundImageObjectKey`;本地试玩ã€ç›´è¾¾æŒ‡å®šå…³å¡å’Œæ­£å¼ `next-level` 推进时,目标关å¡ç¼ºå…³å¡èƒŒæ™¯æ—¶å¿…须继承åŒä½œå“首个å¯ç”¨å…³å¡èƒŒæ™¯ï¼Œä»ç¼ºå¤±æ—¶æ‰æ²¿ç”¨å½“å‰è¿è¡Œæ€å¿«ç…§èƒŒæ™¯æˆ–默认 UI。è¿è¡Œæ€æŒ‰é’®è§†è§‰ä¼˜å…ˆè¯»å–当å‰å…³å¡ `uiSpritesheetImageSrc/uiSpritesheetImageObjectKey`ï¼Œå…ˆæŒ‰é€æ˜Ž alpha 自动边界检测识别 spritesheet ä¸­çš„ç‹¬ç«‹æŒ‰é’®å±•ç¤ºçŸ©å½¢ï¼Œå†æŒ‰åŽŸå›¾ä½ç½®ä»Žå·¦åˆ°å³ã€ä»Žä¸Šåˆ°ä¸‹æ˜ å°„到返回ã€è®¾ç½®ã€ä¸‹ä¸€å…³ã€æç¤ºã€åŽŸå›¾ã€å†»ç»“ï¼›åŒä¸€ç»„ä»¶è¿˜è¦æŒ‰è¾ƒé«˜ alpha é˜ˆå€¼æ´¾ç”Ÿç´§è‡´ç‚¹å‡»çƒ­åŒºï¼Œé€æ˜Žç•™ç™½å’ŒæŸ”边低 alpha 区域尽é‡ä¸å“应点击。检测失败时回退旧固定六格è£åˆ‡ï¼Œç¼ºå¤±æ—¶æ‰ç”¨çŽ°æœ‰å›¾æ ‡æŒ‰é’®å…œåº•ã€‚æœ‰ spritesheet æ—¶ï¼Œè¿”å›žå’Œè®¾ç½®æŒ‰é’®çš„ç‚¹å‡»å®¹å™¨åªæä¾›é€æ˜Žç‚¹å‡»åŒºï¼Œä¸å†å åŠ é»˜è®¤ç™½è‰²åœ†å½¢åº•ï¼›åº•éƒ¨æç¤ºã€åŽŸå›¾ã€å†»ç»“ä¸‰æžšç´ ææŒ‰æ£€æµ‹çŸ©å½¢çš„原始宽高比显示,ä¸èƒ½å¼ºè¡Œæ‹‰ä¼¸æˆæ­£åœ†æˆ–铺满整列。底部é“具区ä¸å†ä½¿ç”¨è¿žç‰‡èƒ¶å›ŠèƒŒæ™¯ï¼Œæç¤ºã€åŽŸå›¾ã€å†»ç»“三个按钮å‡åŒ€åˆ†å¸ƒï¼›è¿è¡Œæ€åªå±•ç¤ºæŒ‰é’®ç´ ææœ¬èº«ï¼Œä¸é¢å¤–å åŠ â€œæç¤º / 原图 / å†»ç»“â€æ–‡å­—。 +- 拼图è¿è¡Œæ€èƒŒæ™¯ä¼˜å…ˆè¯»å–当å‰å…³å¡ `levelBackgroundImageSrc/levelBackgroundImageObjectKey`ï¼Œæ—§æ•°æ®æ‰å…¼å®¹ `uiBackgroundImageSrc/uiBackgroundImageObjectKey`;本地试玩ã€ç›´è¾¾æŒ‡å®šå…³å¡å’Œæ­£å¼ `next-level` 推进时,目标关å¡ç¼ºå…³å¡èƒŒæ™¯æ—¶å¿…须继承åŒä½œå“首个å¯ç”¨å…³å¡èƒŒæ™¯ï¼Œä»ç¼ºå¤±æ—¶æ‰æ²¿ç”¨å½“å‰è¿è¡Œæ€å¿«ç…§èƒŒæ™¯æˆ–默认 UI。è¿è¡Œæ€æŒ‰é’®è§†è§‰ä¼˜å…ˆè¯»å–当å‰å…³å¡ `uiSpritesheetImageSrc/uiSpritesheetImageObjectKey`ï¼Œå…ˆæŒ‰é€æ˜Ž alpha 自动边界检测识别 spritesheet ä¸­çš„ç‹¬ç«‹æŒ‰é’®å±•ç¤ºçŸ©å½¢ï¼Œå†æŒ‰åŽŸå›¾ä½ç½®ä»Žå·¦åˆ°å³ã€ä»Žä¸Šåˆ°ä¸‹æ˜ å°„到返回ã€è®¾ç½®ã€ä¸‹ä¸€å…³ã€æç¤ºã€åŽŸå›¾ã€å†»ç»“ï¼›åŒä¸€ç»„ä»¶è¿˜è¦æŒ‰è¾ƒé«˜ alpha é˜ˆå€¼æ´¾ç”Ÿç´§è‡´ç‚¹å‡»çƒ­åŒºï¼Œé€æ˜Žç•™ç™½å’ŒæŸ”边低 alpha 区域尽é‡ä¸å“应点击。检测失败时回退旧固定六格è£åˆ‡ï¼Œç¼ºå¤±æ—¶æ‰ç”¨çŽ°æœ‰å›¾æ ‡æŒ‰é’®å…œåº•ã€‚æœ‰ spritesheet 时,返回ã€è®¾ç½®å’Œä¸‹ä¸€å…³çš„ç‚¹å‡»å®¹å™¨åªæä¾›é€æ˜Žç‚¹å‡»åŒºï¼Œä¸å†å åŠ é»˜è®¤ç™½è‰²åœ†å½¢åº•ã€èƒ¶å›Šä¸»æŒ‰é’®åº•或é¢å¤–文字;下一关按钮在通关弹窗和底部入å£ä¸­éƒ½ç›´æŽ¥ä½¿ç”¨ spritesheet è£åˆ‡å‡ºçš„ next ç´ æä½œä¸ºæŒ‰é’®æœ¬ä½“。底部æç¤ºã€åŽŸå›¾ã€å†»ç»“ä¸‰æžšç´ ææŒ‰æ£€æµ‹çŸ©å½¢çš„原始宽高比显示,ä¸èƒ½å¼ºè¡Œæ‹‰ä¼¸æˆæ­£åœ†æˆ–铺满整列。底部é“具区ä¸å†ä½¿ç”¨è¿žç‰‡èƒ¶å›ŠèƒŒæ™¯ï¼Œæç¤ºã€åŽŸå›¾ã€å†»ç»“三个按钮å‡åŒ€åˆ†å¸ƒï¼›è¿è¡Œæ€åªå±•ç¤ºæŒ‰é’®ç´ ææœ¬èº«ï¼Œä¸é¢å¤–å åŠ â€œæç¤º / 原图 / å†»ç»“â€æ–‡å­—。 - 推èé¡µæœ¬èº«ä¸æ˜¯ç™»å½•é—¨ç¦å…¥å£ï¼Œæœªç™»å½•用户点击底部或侧边æ çš„æŽ¨è Tab 应直接进入嵌入è¿è¡Œæ€ï¼Œä¸ä¸»åŠ¨æ‰“å¼€ç™»å½•å¼¹çª—ã€‚æŽ¨è页嵌入è¿è¡Œæ€å¿…须按真实身份分æµï¼šå·²ç™»å½•用户或本地已有 access token 时,å¯åŠ¨æ‹¼å›¾å’ŒåŽç»­æŽ’行榜 / 下一关等正å¼è¯·æ±‚ç»§ç»­èµ°è´¦å· Bearerï¼›åªæœ‰ç¡®è®¤ä¸ºåŒ¿å访客时æ‰ç”³è¯·å¹¶é€ä¼  runtime guest token。`/api/runtime/puzzle/runs*` åŽç«¯ç»Ÿä¸€æŽ¥å— `RuntimePrincipal`,å¯è¯†åˆ«è´¦å·ç”¨æˆ·å’ŒåŒ¿å runtime guest;推èå¡ç‰‡çš„åŽå°è¯»å†™è¯·æ±‚ä»ä½¿ç”¨ local auth impact,é¿å…å•å¡ 401 清空整站登录æ€ã€‚创作ã€ä¸ªäººä½œå“ã€åˆ é™¤ã€å‘布ã€Remix ç­‰è´¦å·æˆ–所有æƒåŠ¨ä½œä»ä¿æŒæ™®é€šç”¨æˆ·é‰´æƒã€‚ - 拼图è¿è¡Œæ€æ£‹ç›˜ä¸å åŠ åˆ†å—è’™ç‰ˆã€æè¾¹ã€é˜´å½±ã€é€‰ä¸­åº•色或åˆå¹¶å— SVG 轮廓;拼图片本体需è¦è£åˆ‡ä¸ºåœ†è§’形状,å•å—使用独立圆角è£åˆ‡ï¼Œåˆå¹¶å—使用 SVG 原生 `clipPath` è£åˆ‡æ•´ä½“外轮廓,外凸角和内凹角分别计算åŠå¾„,内凹角åŠå¾„è¦æ¯”外凸角更明显以é¿å…手机 WebView 中看起æ¥ä»æ˜¯ç›´è§’。原图é“å…·åªåœ¨ç”¨æˆ·ä¸»åŠ¨ç¡®è®¤åŽæ‰“开独立原图查看层,ä¸åœ¨å½“剿‹¼å›¾æ£‹ç›˜ä¸Šå åŠ åŽŸå›¾ã€‚ - 拼图è¿è¡Œæ€æ‹–æ‹½å¿…é¡»å®Œå…¨è·Ÿéšæ‰‹æŒ‡æˆ–é¼ æ ‡ä½ç½®ï¼Œ`pointermove` æœŸé—´å³æ—¶å†™å…¥å¯è§æ‹¼å—çš„ transform,ä¸ä¾èµ–等待åŽç«¯å›žåŒ…ã€React 釿¸²æŸ“或下一帧动画队列;进入拖动åŽä¸å±•示拼å—é€‰ä¸­æ€æˆ–â€œå·²é€‰æ‹©â€æç¤ºï¼Œæ¾æ‰‹åŽå†æäº¤ç›®æ ‡æ ¼åŒæ­¥è§„则真相。 diff --git a/docs/ã€é¡¹ç›®åŸºçº¿ã€‘当å‰äº§å“与工程约æŸ-2026-05-15.md b/docs/ã€é¡¹ç›®åŸºçº¿ã€‘当å‰äº§å“与工程约æŸ-2026-05-15.md index 09dbe102..68c05a44 100644 --- a/docs/ã€é¡¹ç›®åŸºçº¿ã€‘当å‰äº§å“与工程约æŸ-2026-05-15.md +++ b/docs/ã€é¡¹ç›®åŸºçº¿ã€‘当å‰äº§å“与工程约æŸ-2026-05-15.md @@ -95,7 +95,7 @@ server-rs + Axum + SpacetimeDB 7. 主站入å£å·²é”定移动端页é¢çº§ç¼©æ”¾ï¼›å•个游æˆé¡µé¢ä¸è¦å†é‡å¤å®žçŽ°æ•´é¡µç¼©æ”¾é”定。 8. 图åƒè¾“入通用 UI 统一走 `src/components/common/CreativeImageInputPanel.tsx`ã€‚å¤–å±‚é¡µé¢æŒæœ‰ä¸šåŠ¡çŠ¶æ€ï¼Œç»„ä»¶åªæ‰¿æ‹…上传å¡ã€é¢„览ã€å‚考图缩略图ã€AI é‡ç»˜å¼€å…³ã€é”™è¯¯å±•示和æäº¤æŒ‰é’®ã€‚ 9. å‘现页 `分类` å­é¢‘é“的筛选必须打开独立 dialog / drawer / modal,至少支æŒçŽ©æ³•ç±»åž‹è¿‡æ»¤ä¸ŽæŽ’åºåˆ‡æ¢ï¼›ç­›é€‰ç»“果为空时显示空状æ€ï¼Œä¸æŠŠç­›é€‰å†…容展开在当å‰åˆ—表下方。 -10. 移动端“我的â€é¡µé¡¶éƒ¨å“牌行承载扫ç å’Œè®¾ç½®å…¥å£ï¼Œæ­£æ–‡æŒ‰å‚考图顺åºç»„ç»‡ä¸ºå¤´åƒ / 昵称 / é™¶æ³¥å·ã€ä¼šå‘˜æ¨ªå¹…ã€ä¸‰å¼ ç»Ÿè®¡å¡ã€æ¯æ—¥ä»»åŠ¡ã€äº”项常用功能宫格ã€è®¾ç½®å…¥å£å’Œæ³•律信æ¯ï¼›`media/profile/` 中的陶泥素æä½œä¸ºè¯¥é¡µå›¾å½¢èµ„产。常用功能宫格固定承载泥点充值ã€é‚€è¯·å¥½å‹ã€å…‘æ¢ç ã€çŽ©å®¶ç¤¾åŒºã€å馈与建议;页é¢ä¸å†æä¾›ç‹¬ç«‹å­˜æ¡£æŒ‰é’®å…¥å£ï¼Œä¹Ÿä¸åœ¨åº•部ä¿ç•™æ—§çš„å¡«é‚€è¯·ç æ¬¡çº§å…¥å£ã€‚填邀请ç åªç”±é‚€è¯·é“¾æŽ¥ query 或其它明确引导打开独立弹窗,ä¸ä½œä¸ºâ€œæˆ‘çš„â€é¡µå¸¸é©»æŒ‰é’®ã€‚ +10. 移动端“我的â€é¡µé¡¶éƒ¨å“牌行承载扫ç å’Œè®¾ç½®å…¥å£ï¼Œæ­£æ–‡æŒ‰å‚考图顺åºç»„ç»‡ä¸ºå¤´åƒ / 昵称 / é™¶æ³¥å·ã€ä¼šå‘˜æ¨ªå¹…ã€ä¸‰å¼ ç»Ÿè®¡å¡ã€æ¯æ—¥ä»»åŠ¡ã€äº”项常用功能宫格ã€è®¾ç½®å…¥å£å’Œæ³•律信æ¯ï¼›`media/profile/` 中的陶泥素æä½œä¸ºè¯¥é¡µå›¾å½¢èµ„产。常用功能宫格固定承载泥点充值ã€é‚€è¯·å¥½å‹ã€å…‘æ¢ç ã€çŽ©å®¶ç¤¾åŒºã€å馈与建议;当å‰åªå±•ç¤ºå››é¡¹å¸¸é©»å…¥å£æ—¶å¿…须按四列铺满整行,ä¸ä¿ç•™äº”列网格导致左对é½ç©ºä½ã€‚页é¢ä¸å†æä¾›ç‹¬ç«‹å­˜æ¡£æŒ‰é’®å…¥å£ï¼Œä¹Ÿä¸åœ¨åº•部ä¿ç•™æ—§çš„å¡«é‚€è¯·ç æ¬¡çº§å…¥å£ã€‚填邀请ç åªç”±é‚€è¯·é“¾æŽ¥ query 或其它明确引导打开独立弹窗,ä¸ä½œä¸ºâ€œæˆ‘çš„â€é¡µå¸¸é©»æŒ‰é’®ã€‚ 11. “我的â€é¡µæ¯æ—¥ä»»åŠ¡å¡å¿…须展示åŽç«¯ `/api/profile/tasks` 返回的当å‰ä»»åŠ¡æ‘˜è¦ï¼ŒåŒ…括奖励泥点数ã€è¿›åº¦å’Œé¢†å– / åŽ»å®Œæˆ / 已完æˆçжæ€ï¼›ä»»åС领喿ˆåŠŸåŽï¼Œå¡ç‰‡æ‘˜è¦å¿…须跟éšè¿”回的任务中心数æ®åŒæ­¥åˆ·æ–°ï¼Œä¸èƒ½ç»§ç»­ç¡¬ç¼–ç  `0 / 1` æˆ–åªæ›´æ–°å¼¹çª—内任务列表。 12. “我的â€é¡µæ³¥ç‚¹ã€æ¸¸æˆæ—¶é•¿ã€å·²çŽ©æ¸¸æˆæ•°é‡ä¸‰å¼ ç»Ÿè®¡å¡åªå±•示å„自标签和值,三个统计 icon 使用å°å°ºå¯¸æ™®é€š UI æ¡£ä½ï¼Œå†…容䏿¢è¡Œï¼Œä¸åœ¨ç»Ÿè®¡åŒºåº•éƒ¨å±•ç¤ºâ€œæ›´æ–°äºŽâ€æ—¶é—´ï¼›ç§»åŠ¨ç«¯æ˜µç§°ã€ä¼šå‘˜å¡ã€æ¯æ—¥ä»»åŠ¡ã€å¸¸ç”¨åŠŸèƒ½å’Œæ³•å¾‹ä¿¡æ¯ä¹Ÿåº”ä¿æŒ `10px` 到 `14px` 的普通 UI å­—å·åŒºé—´ï¼Œé¿å…å±•ç¤ºçº§å­—å·æŒ¤åŽ‹å†…å®¹ã€‚ 13. 移动端“我的â€é¡µéœ€è¦å…¼å®¹çª„å±ï¼šå¤´åƒ / 昵称 / é™¶æ³¥å·ã€ä¸‰å¼ ç»Ÿè®¡å¡ã€æ¯æ—¥ä»»åŠ¡ã€äº”项常用功能和法律信æ¯éƒ½å¿…须能在底部固定 TabBar 上方完整滚动露出,ä¸å¾—与底部 dockã€åˆ˜æµ· safe-area 或相邻 UI å…ƒç´ é®æŒ¡é‡å ã€‚ diff --git a/src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx b/src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx index e2604ace..41bb3d1b 100644 --- a/src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx +++ b/src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx @@ -617,7 +617,11 @@ test('é€šå…³åŽæ˜¾ç¤ºç»“ç®—å¼¹çª—ã€æŽ’è¡Œæ¦œå’Œä¸‹ä¸€å…³æŒ‰é’®', () => { expect(within(dialog).getByText('#1')).toBeTruthy(); expect(within(dialog).getByText('测试作者')).toBeTruthy(); - fireEvent.click(within(dialog).getByRole('button', { name: '下一关' })); + const nextButton = within(dialog).getByRole('button', { name: '下一关' }); + expect(nextButton.textContent).toContain('下一关'); + expect(nextButton.querySelector('[data-puzzle-ui-sprite="next"]')).toBeNull(); + + fireEvent.click(nextButton); expect(onAdvanceNextLevel).toHaveBeenCalledTimes(1); vi.useRealTimers(); @@ -876,13 +880,16 @@ test('è¿è¡Œæ€ç”¨ UI spritesheet 原图检测矩形è£åˆ‡è¿”回设置下一关 expect( screen.getByRole('button', { name: '打开拼图设置' }).className, ).not.toContain('rounded-full'); - const nextSprite = screen - .getByRole('button', { name: '下一关' }) - .querySelector('[data-puzzle-ui-sprite="next"]') as HTMLElement | null; - expect(nextSprite).toBeTruthy(); - expect(nextSprite?.style.backgroundSize).toBe('320% 480%'); - expect(nextSprite?.style.backgroundPosition).toBe('50% 57.89473684210527%'); - expect(screen.getByRole('button', { name: '下一关' }).textContent).toBe(''); + const nextButton = screen.getByRole('button', { name: '下一关' }); + expect(nextButton.dataset.puzzleUiSprite).toBe('next'); + expect(nextButton.querySelector('[data-puzzle-ui-sprite="next"]')).toBeNull(); + expect(nextButton.style.backgroundSize).toBe('320% 480%'); + expect(nextButton.style.backgroundPosition).toBe('50% 57.89473684210527%'); + expect(nextButton.className).not.toContain('puzzle-runtime-primary-button'); + expect(nextButton.className).not.toContain('rounded-full'); + expect(nextButton.className).not.toContain('px-5'); + expect(nextButton.className).not.toContain('py-2.5'); + expect(nextButton.textContent).toBe(''); expect( screen .getByRole('button', { name: 'æç¤º' }) @@ -962,7 +969,7 @@ test('è¿è¡Œæ€åœ¨åªæœ‰ UI 背景 objectKey æ—¶ä»æ¸²æŸ“生æˆèƒŒæ™¯', () => { expect(backgroundImage).toBeTruthy(); }); -test('关闭通关弹窗åŽä¿ç•™åº•部下一关入å£', () => { +test('关闭通关弹窗åŽä¿ç•™åº•部下一关入å£', async () => { vi.useFakeTimers(); const onAdvanceNextLevel = vi.fn(); const runWithoutRecommendedNextProfile: PuzzleRunSnapshot = { @@ -988,10 +995,31 @@ test('关闭通关弹窗åŽä¿ç•™åº•部下一关入å£', () => { onAdvanceNextLevel={onAdvanceNextLevel} />, ); + await act(async () => {}); act(() => { vi.advanceTimersByTime(1_400); }); + const dialog = screen.getByRole('dialog', { name: '通关完æˆ' }); + const dialogNextButton = within(dialog).getByRole('button', { + name: '下一关', + }); + expect(dialogNextButton.dataset.puzzleUiSprite).toBe('next'); + expect( + dialogNextButton.querySelector('[data-puzzle-ui-sprite="next"]'), + ).toBeNull(); + expect(dialogNextButton.style.backgroundSize).toBe('320% 480%'); + expect(dialogNextButton.style.backgroundPosition).toBe( + '50% 57.89473684210527%', + ); + expect(dialogNextButton.className).not.toContain( + 'puzzle-runtime-primary-button', + ); + expect(dialogNextButton.className).not.toContain('rounded-full'); + expect(dialogNextButton.className).not.toContain('px-5'); + expect(dialogNextButton.className).not.toContain('py-2.5'); + expect(dialogNextButton.textContent).toBe(''); + act(() => { fireEvent.click(screen.getByRole('button', { name: '关闭通关弹窗' })); }); @@ -1050,9 +1078,8 @@ test('推è页关闭通关弹窗åŽä¿ç•™åº•部下一关入å£ä¸”ä¸å åР䏋 expect(screen.queryByRole('dialog', { name: '通关完æˆ' })).toBeNull(); const nextButton = screen.getByRole('button', { name: /下一关/u }); expect(nextButton).toBeTruthy(); - expect( - nextButton.querySelector('[data-puzzle-ui-sprite="next"]'), - ).toBeTruthy(); + expect(nextButton.dataset.puzzleUiSprite).toBe('next'); + expect(nextButton.querySelector('[data-puzzle-ui-sprite="next"]')).toBeNull(); expect(nextButton.textContent?.trim()).toBe(''); vi.useRealTimers(); }); diff --git a/src/components/puzzle-runtime/PuzzleRuntimeShell.tsx b/src/components/puzzle-runtime/PuzzleRuntimeShell.tsx index 92f395de..ee0bc2e2 100644 --- a/src/components/puzzle-runtime/PuzzleRuntimeShell.tsx +++ b/src/components/puzzle-runtime/PuzzleRuntimeShell.tsx @@ -162,6 +162,15 @@ function PuzzleUiSprite({ ); } +function resolvePuzzleUiSpriteAspectRatio( + kind: PuzzleUiSpriteKind, + layout: PuzzleUiSpritesheetLayout | null, + fallback: string, +) { + const region = layout?.regions[kind]; + return region ? `${region.width} / ${region.height}` : fallback; +} + function buildMergedGroupViewModels( groups: PuzzleMergedGroupState[], pieces: PuzzleBoardPieceViewModel[], @@ -1221,6 +1230,22 @@ export function PuzzleRuntimeShell({ const clearResultOverlayClassName = embedded ? `platform-ui-shell platform-theme ${platformThemeClass} puzzle-runtime-shell puzzle-runtime-modal-overlay puzzle-runtime-modal-overlay--fixed flex items-center justify-center px-4 py-6 backdrop-blur-sm` : 'puzzle-runtime-modal-overlay absolute inset-0 z-40 flex items-center justify-center px-4 py-6 backdrop-blur-sm'; + const nextSpriteButtonClassName = + 'inline-flex h-12 appearance-none items-center justify-center border-0 bg-transparent p-0 leading-none shadow-none transition hover:brightness-105 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--platform-button-primary-border)] disabled:cursor-not-allowed disabled:opacity-45'; + const nextSpriteButtonStyle = hasUiSpritesheet + ? { + ...buildPuzzleUiSpriteBackgroundStyle({ + src: resolvedUiSpritesheetImage, + kind: 'next', + layout: uiSpritesheetLayout, + }), + aspectRatio: resolvePuzzleUiSpriteAspectRatio( + 'next', + uiSpritesheetLayout, + '2 / 1', + ), + } + : undefined; const handleBackRequest = () => { if (hideExitControls) { return; @@ -1457,6 +1482,8 @@ export function PuzzleRuntimeShell({ ) : null} @@ -1933,6 +1969,7 @@ export function PuzzleRuntimeShell({ ) : null} diff --git a/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx b/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx index 9cc830bb..d9ef46eb 100644 --- a/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx +++ b/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx @@ -2412,6 +2412,21 @@ test('mobile profile page matches the reference layout sections', async () => { .querySelector('.platform-profile-shortcut-grid') ?.classList.contains('platform-profile-shortcut-grid'), ).toBe(true); + expect( + shortcutRegion + .querySelector('.platform-profile-shortcut-grid') + ?.className, + ).toContain('!grid-cols-4'); + expect( + shortcutRegion + .querySelector('.platform-profile-shortcut-grid') + ?.className, + ).toContain('w-full'); + for (const shortcutButton of shortcutRegion.querySelectorAll( + '.platform-profile-shortcut-button', + )) { + expect(shortcutButton.className).toContain('w-full'); + } for (const label of [ '泥点充值', 'å…‘æ¢ç ', diff --git a/src/components/rpg-entry/RpgEntryHomeView.tsx b/src/components/rpg-entry/RpgEntryHomeView.tsx index 3e6d4957..51e2db1e 100644 --- a/src/components/rpg-entry/RpgEntryHomeView.tsx +++ b/src/components/rpg-entry/RpgEntryHomeView.tsx @@ -2434,7 +2434,7 @@ function ProfileShortcutButton({