feat(ui): 按原版样式重建商店页布局

This commit is contained in:
TermiNexus committed 2026-10-27 00:12:42 +08:00
1 parent 3ab2567c00
commit c23690669d
4 files changed
+877 -147

No files matched your search

+130
View File
@@ -0,0 +1,130 @@
# TankIC 混淆符号字典(原版 Kotlin/JS UI)
> 范围:`3DTank/src/module_82347.js`(UI 组件/样式/资源)、`3DTank/src/module_63383.js`(CSS DSL)、`3DTank/src/module_37036.js`(Kotlin 运行时)、`3DTank/src/index.json`(资源模块索引)。`3DTank/src` 为原版拆包,`3dtank.com/play/static/js/unpacked` 为同构微修改版;本表行号优先引用前者。
> 方法:静态解析 `Sq` / `Lq` 注册、样式类构造器、`Eq("...")` 字段、`jq(...)` 访问器、IIFE 参数表;并用 `acorn` 作用域分析确认资源参数的真实引用。
> 证据格式:`文件:行号`。资源模块优先引用 `3DTank/src/index.json` 的 `id` / `path` 行;压缩版 `main.6469b966.js` 仅作同构对照,不再作为主证据。
> 同构核对:`3DTank/src/module_82347.js` 与 `3dtank.com/play/static/js/unpacked/module_82347.js` 仅 `137040-137047` 内容不同(本地 SRV 显示补丁),行号一致;`module_63383/37036/59131/24923` 两版哈希一致。
## 0. 读法
- `Sq = To.$_$.ld`(`3DTank/src/module_82347.js:1326`)→ `3DTank/src/module_37036.js:10177` → `ie()`(`3DTank/src/module_37036.js:1276-1277`,内部调用 `ne("object", ...)`;`ne` 定义在 `:1271`)。它是 Kotlin 类元数据注册,不是 CSS 注册。
- 真正的样式字段在样式类构造器内:`this.<field> = this.aux([], <函数>).suv(this, Eq("<语义名>", ...))`。
- 访问器形如 `jq(<类>).<方法> = function () { ... this.<field>.dk(this, Eq("<语义名>"...)) }`;因此 `字段=访问器` 可一一对应。
- 带参数、且不绑定单一字段的方法单列为“参数化/共享样式方法”,不强行归入字段。
## 1. 样式类注册表
| 类名 | 语义名 | 文件:行号 | 已确认的字段/访问器 |
|---|---|---|---|
| `d7n` | `MainScreenComponentStyle` | `3DTank/src/module_82347.js:107879`(def `67488`;acc `173741-173921`) | `containerPanel=k1pz`; `containerForMenuGradient=l1pz`; `gradientBlock=m1pz`; `gradientBlockRight=n1pz`; `buttonPlay=e1o4`; `blockMainMenu=o1pz`; `noNew=p1pz`; `new=q1pz`; `playButtonContainer=r1pz`; `disabled=w1lw`; `disabledButtonPlay=s1pz`; `activeItem=t1pz`; `commonBlock=u1ju` |
| `fni` | `UserInfoContainerStyle` | `3DTank/src/module_82347.js:107924`(def `71520`;acc `177288-177638`) | `rankIconContainer=o1qe`; `containerRightBorder=p1qe`; `rankIconContainerClickable=q1qe`; `userNameRank=r1qe`; `textDecoration=s1qe`; `containerUserNameRank=t1qe`; `blockLeftPanel=y1qd`; `userTitleContainer=u1qe`; `titleRankIcon=v1qe`; `containerProgressMainScreen=w1qe`; `containerParameterProgress=x1qe`; `xpIcon=y1qe`; `progressValue=y1iq`; `tooltipContainer=u1je`; `premiumBenefitsContainer=u1qa`; `tooltipTimer=z1q9`; `grayColor=m1qa`; `regularUser=r1ih`; `friendUser=t1ih`; `clanMateUser=s1ih`; `blockForIconTankiOnline=n1qe`; `accountRemovalTimer=a1qe`;参数化:`u1iz(t)` @`177438` |
| `Yni` | `UserScoreComponentStyle` | `3DTank/src/module_82347.js:107927`(def `72033`;acc `177929-178089`) | `coinsContainer=b1ni`; `temporaryCashContainer=j1qd`; `temporaryCashButton=k1qd`; `blockRightPanel=z1qd`; `iconCrystal=o1k9`; `addRubyCrystal=m1qd`; `addCoins=i1qd`; `coinBlock=g1qd`; `coinIcon=h1qd`;参数化:`n1qd(t)` @`177974`、`l1qd(t)` @`178000`、`z1qc(t)` @`178059` |
| `d9` | `UserGroupTitleButtonComponentStyle` | `3DTank/src/module_82347.js:106987`(def `5061`;acc `110368-110398`) | `userGroupTitle=u1ii`; `commonBlockGroup=w1ii`; `commonUserIconsBlockGroup=v1ii` |
| `d1n` | `BreadcrumbsComponentStyle` | `3DTank/src/module_82347.js:107855`(def `66052`;acc `172496-172601`) | `headerContainer=h1pt`; `backButton=y1ie`; `rightButtonsContainer=d1pt`; `exitGameButton=e1pt`; `logout=i1pt`; `iconLogout=j1pt`; `rootTitle=o1om`; `breadcrumbs=g1pt` |
| `kNi` | `SettingsComponentStyle` | `3DTank/src/module_82347.js:108144`(def `89525`;acc `193943-194138`) | `blockContentOptions=n1s2`; `scroll=r1k3`; `scrollCreateBattle=p1jd`; `settingsBlockOptions=u1rv`; `containerBlock=n1n3`; `borderLineOption=d1rn`; `commonBlock=u1ju`; `rowBlock=q1s2`; `textHeadlineOptions=f1rn`; `scrollingMenu=o1jn`; `dropDownTitle=e1rt`; `dropDown=x1nc`; `dropdownPlaceholder=z1mg`; `slider=e1nj` |
| `EMi` | `SettingsMenuComponentStyle` | `3DTank/src/module_82347.js:108168`(def `90329`;acc `197540-197588`) | `slideMenuOptions=g1s7`; `yellowLineMenuOptions=h1s7`; `menuItemOptions=a1s7`; `activeItemOptions=b1s7`;共享样式:`f1s7()` @`197540` 返回 `zMi`(见 1.1) |
| `UPi` | `SettingsButtonsComponentStyle` | `3DTank/src/module_82347.js:108141`(def `89326`;acc `193821-193851`) | `containerGroupButton=h1s2`; `buttonsWidthBackReset=g1s2`; `buttonSaveLeave=i1s2` |
| `J7t` | `DropDownStyle` | `3DTank/src/module_82347.js:107449`(def `34073`;acc `141248-141385`) | `dropdownRoot=h1mh`; `dropdownControl=i1mh`; `dropdownPlaceholder=z1mg`; `dropdownMenu=k1mh`; `outerContainerStyle=a1md`; `innerContainerStyle=r1mc`; `dataListControl=a1mh`; `arrowDropDown=j1mh`;参数化:`f1mh(t)` @`141263`、`c1mh(t)` @`141338` |
| `u7t` | `ContextMenuStyle` | `3DTank/src/module_82347.js:107442`(def `33638`;acc `140826-140934`) | `menu=y1mf`; `menuItem=z1mf`; `colorText=z1li`; `disabledMenuItem=a1mg`; `menuItemRank=b1mg`; `decoration=c1mg`;参数化:`d1mg(t)` @`140856`、`e1mg(t,n,i,r)` @`140934` |
| `QBi` | `CheckBoxComponentStyle` | `3DTank/src/module_82347.js:108203`(def `92742`;acc `200405-200446`) | `oneSetting=w1md`;参数化:`d1sh(t)` @`200420`、`x1md(t,n)` @`200446` |
| `Z0t` | `CheckBoxStyle` | `3DTank/src/module_82347.js:107431`(def `33272`;acc `140535-140563`) | `checkbox=k1md`;参数化:`l1md(t,n)` @`140550`、`n1md(t,n,i)` @`140553`、`m1md(t,n,i)` @`140563` |
| `EUi` | `InputRangeComponentStyle` | `3DTank/src/module_82347.js:108205`(def `92965`;acc `200584-200614`) | `blockSoundVolume=r1sh`; `soundVolume=s1sh`; `range=hd` |
| `o2n` | `HeaderCommonStyle` | `3DTank/src/module_82347.js:107868`(def `67109`;acc `173096-173111`) | `icons=sdy`; `iconFont=e1ka` |
表中 `acc` 是 `jq(...)` 方法范围,可能包含 1.1 节的参数化方法;`字段=访问器` 只列直接读取 `this.<field>.dk(...)` 的方法。
### 1.1 参数化/共享样式方法(已核对,不是字段)
| 方法 | 参数/返回 | 调用点 | 已核对语义 | 证据文件:行号 |
|---|---|---|---|---|
| `fni.u1iz(t)` | 百分比数值 | `3DTank/src/module_82347.js:177904` | XP 进度条容器;子节点宽度按 `t%` | `3DTank/src/module_82347.js:177438-177460` |
| `Yni.n1qd(t)` | 布尔 cursor 开关 | `3DTank/src/module_82347.js:176977` | 水晶货币行样式 | `3DTank/src/module_82347.js:177974-177980` |
| `Yni.l1qd(t)` | 布尔 cursor 开关 | `3DTank/src/module_82347.js:176951` | 红宝石货币行样式(带底边框) | `3DTank/src/module_82347.js:178000-178009` |
| `Yni.z1qc(t)` | 布尔 cursor 开关 | `3DTank/src/module_82347.js:176918` | 金币按钮行样式 | `3DTank/src/module_82347.js:178059-178069` |
| `EMi.f1s7()` | 返回共享函数 `zMi` | `3DTank/src/module_82347.js:197505` | 设置菜单项共享布局样式 | `3DTank/src/module_82347.js:197540`; `zMi` 定义 `3DTank/src/module_82347.js:90245-90258` |
| `J7t.f1mh(t)` | 布尔 `isWhiteTheme` | `3DTank/src/module_82347.js:141083` | 下拉容器主题样式 | `3DTank/src/module_82347.js:141263-141271` |
| `J7t.c1mh(t)` | 布尔 `isWhiteTheme` | `3DTank/src/module_82347.js:33852` | 下拉滚动条主题样式 | `3DTank/src/module_82347.js:141338-141348` |
| `u7t.d1mg(t)` | 布尔分支 | `3DTank/src/module_82347.js:141061` | 菜单项样式(普通/选中分支) | `3DTank/src/module_82347.js:140856-140868` |
| `u7t.e1mg(t,n,i,r)` | 4 个参数 | `3DTank/src/module_82347.js:143351` | 菜单项复合样式 | `3DTank/src/module_82347.js:140934-140946` |
| `QBi.d1sh(t)` | 布尔 disabled | `3DTank/src/module_82347.js:93016` | 设置行/复选框行样式 | `3DTank/src/module_82347.js:200420-200439` |
| `QBi.x1md(t,n)` | 元素 + 布尔 | `3DTank/src/module_82347.js:33330`, `93020` | 设置输入/开关行样式 | `3DTank/src/module_82347.js:200446-200463` |
| `Z0t.l1md(t,n)` | 元素 + 布尔 | `3DTank/src/module_82347.js:33315`, `140502` | 开关容器基础样式 | `3DTank/src/module_82347.js:140550-140551` |
| `Z0t.n1md(t,n,i)` | 元素 + 两个布尔 | `3DTank/src/module_82347.js:33321`, `140530` | 开关轨道样式 | `3DTank/src/module_82347.js:140553-140561` |
| `Z0t.m1md(t,n,i)` | 元素 + 两个布尔 | `3DTank/src/module_82347.js:140530` | 圆形开关/勾选样式 | `3DTank/src/module_82347.js:140563-140575` |
## 2. CSS DSL 映射表
仅列已由 `module_63383.js` 函数体 `$e("<cssProperty>")` 直接确认的项。
| 压缩名 | 导出键 | 实际函数名 | CSS 属性 | 证据文件:行号 |
|---|---|---|---|---|
| `Wy` | `Ho.$_$.sb` | `Pc` | `width` | `3DTank/src/module_82347.js:1398`; `3DTank/src/module_63383.js:8082`, `5120`, `5126` |
| `Vy` | `Ho.$_$.b9` | `ua` | `height` | `3DTank/src/module_82347.js:1399`; `3DTank/src/module_63383.js:7803`, `3566`, `3571` |
| `R$` | `Ho.$_$.ka` | `rc` | `position` | `3DTank/src/module_82347.js:1440`; `3DTank/src/module_63383.js:7931`, `4528`, `4533` |
| `H$` | `Ho.$_$.b8` | `$h` | `cursor` | `3DTank/src/module_82347.js:1451`; `3DTank/src/module_63383.js:7670`, `2937`, `2942` |
| `o$` | `Ho.$_$.p9` | `za` | `marginRight` | `3DTank/src/module_82347.js:1413`; `3DTank/src/module_63383.js:7885`, `3862`, `3867` |
| `jj` | `Ho.$_$.x6` | `Uu` | `borderBottom` | `3DTank/src/module_82347.js:1541`; `3DTank/src/module_63383.js:7610`, `512`, `517` |
| `fS` | `Ho.$_$.i8` | `Ah` | `flexDirection` | `3DTank/src/module_82347.js:1581`; `3DTank/src/module_63383.js:7694`, `3085`, `3090` |
| `pS` | `Ho.$_$.h6` | `h_` | `alignItems` | `3DTank/src/module_82347.js:1583`; `3DTank/src/module_63383.js:7577`, `1642`, `1647` |
| `cx` | `Ho.$_$.g9` | `oa` | `justifyContent` | `3DTank/src/module_82347.js:1471`; `3DTank/src/module_63383.js:7853`, `3603`, `3608` |
| `_x` | `Ho.$_$.i6` | `c_` | `alignSelf` | `3DTank/src/module_82347.js:1468`; `3DTank/src/module_63383.js:7578`, `1679`, `1684` |
| `Jy` | `Ho.$_$.c8` | `jh` | `display` | `3DTank/src/module_82347.js:1403`; `3DTank/src/module_63383.js:7671`, `2974`, `2979` |
| `LS` | `Ho.$_$.fa` | `Ya` | `paddingLeft` | `3DTank/src/module_82347.js:1608`; `3DTank/src/module_63383.js:7924`, `4380`, `4385` |
| `BS` | `Ho.$_$.ga` | `Ja` | `paddingRight` | `3DTank/src/module_82347.js:1609`; `3DTank/src/module_63383.js:7925`, `4417`, `4422` |
> `Ho` 即模块 `63383`:`3DTank/src/module_82347.js:212990` 的 `i(63383)` 对应参数 `Ho`。`3DTank/src/module_24923.js:1313` 又依赖 `63383` 做样式注入/类名生成;本表只确认 `63383` 中的属性名字面量,不把 `24923` 的生成器内部实现当作语义。13 项导出键→函数→`$e` 属性已逐项机器校验一致。
## 3. 资源模块表
`3DTank/src/module_82347.js` 的资源参数名 → 调用参数 `i(<id>)` → `3DTank/src/index.json` 中同 id 的 `path` 字段(原版拆包索引;压缩版 `main.6469b966.js` 为同构对照)。前 9 行为任务指定资源,后 8 行为同组件/核对项直接引用的补充项。
| 模块 id | static/images/... 文件名 | 用途 | 证据文件:行号 |
|---|---|---|---|
| `32475` | `ruby.ce62efbc.svg` | `ruby` 红宝石图标 | 参数 `cn` `3DTank/src/module_82347.js:123`;`i(32475)` `:212634`;`3DTank/src/index.json:2375-2377`;使用 `3DTank/src/module_82347.js:176956` |
| `91844` | `crystalSmall.242e6c15.svg` | `crystalSmall` 小水晶图标 | 参数 `fo` `3DTank/src/module_82347.js:448`、`I` `:37`;`i(91844)` `:212548`;`3DTank/src/index.json:6245-6247`;使用 `3DTank/src/module_82347.js:96447`, `176985` |
| `39856` | `addRuby.ff47c8be.svg` | `addRuby` 红宝石加号 | 参数 `Ku` `3DTank/src/module_82347.js:375`;`i(39856)` `:212886`;`3DTank/src/index.json:2845-2847`;使用 `3DTank/src/module_82347.js:176972` |
| `33190` | `addCrystal.af9a397c.svg` | `addCrystal` 水晶加号 | 参数 `Yu` `3DTank/src/module_82347.js:376`;`i(33190)` `:212887`;`3DTank/src/index.json:2430-2432`;使用 `3DTank/src/module_82347.js:176996` |
| `17881` | `groupUser.67c9ec61.svg` | `groupUser` 组队成员剪影 | 参数 `m` `3DTank/src/module_82347.js:24`;`i(17881)` `:212535`;`3DTank/src/index.json:1320-1322`;使用 `3DTank/src/module_82347.js:4860` |
| `51963` | `iconButtonPremium.bc17a592.svg` | `iconButtonPremium` 高级账号图标 | 参数 `H` `3DTank/src/module_82347.js:50`;`i(51963)` `:212561`;`3DTank/src/index.json:3610-3612`;使用 `3DTank/src/module_82347.js:176468` |
| `39688` | `iconTankiOnline.2a9c236f.svg` | `iconTankiOnline` Tanki Online 标志 | 参数 `Ju` `3DTank/src/module_82347.js:378`;`i(39688)` `:212889`;`3DTank/src/index.json:2825-2827`;使用 `3DTank/src/module_82347.js:177224` |
| `70556` | `fullscreen.0b4547f5.svg` | `fullscreen` 全屏图标 | 参数 `zn` `3DTank/src/module_82347.js:132`;`i(70556)` `:212643`;`3DTank/src/index.json:4830-4832`;使用 `3DTank/src/module_82347.js:137380` |
| `88309` | `iconBack.62869951.svg` | `iconBack` 返回/退出箭头 | 参数 `g` `3DTank/src/module_82347.js:23`;`i(88309)` `:212534`;`3DTank/src/index.json:5960-5962`;使用 `3DTank/src/module_82347.js:66013`, `4762` |
| `98628` | `iconBackArrow.9f950ca9.svg` | Breadcrumbs 子页返回箭头 | 参数 `kr` `3DTank/src/module_82347.js:239`;`i(98628)` `:212750`;`3DTank/src/index.json:6635-6637`;`IconStyle.iconBackArrow` @`3DTank/src/module_82347.js:211486`,使用 `:172388` |
| `82126` | `back.72d79619.svg` | 触摸控制编辑器返回键 | 参数 `uo` `3DTank/src/module_82347.js:440`;`i(82126)` `:212951`;`3DTank/src/index.json:5555-5557`;使用 `3DTank/src/module_82347.js:92464`(`TouchControlsEditorComponentStyle.backButton` @`:108202`) |
| `91451` | `fullscreenOff.5691cd5d.svg` | 退出全屏图标(与 `fullscreen` 成对) | 参数 `bn` `3DTank/src/module_82347.js:133`;`i(91451)` `:212644`;`3DTank/src/index.json:6200-6202`;使用 `3DTank/src/module_82347.js:137380` |
| `86552` | `coin.3a16e831.svg` | 金币图标 | 参数 `Vi` `3DTank/src/module_82347.js:212`;`i(86552)` `:212640`;`3DTank/src/index.json:5855-5857`;使用 `3DTank/src/module_82347.js:176926` |
| `22511` | `addCoin.cc7a25d3.svg` | 金币加号 | 参数 `Wu` `3DTank/src/module_82347.js:373`;`i(22511)` `:212884`;`3DTank/src/index.json:1695-1697`;使用 `3DTank/src/module_82347.js:176941` |
| `22198` | `caution.b9d12319.svg` | 临时货币按钮图标 | 参数 `Vu` `3DTank/src/module_82347.js:374`;`i(22198)` `:212885`;`3DTank/src/index.json:1665-1667`;使用 `3DTank/src/module_82347.js:176904`(`UserScoreComponentStyle.temporaryCashButton` @`:176901`) |
| `16849` | `empty.c3b3f789.svg` | Tanki Online 标志占位图(`mr4_1` 为真时替代 logo) | 参数 `Xu` `3DTank/src/module_82347.js:377`;`i(16849)` `:212888`;`3DTank/src/index.json:1240-1242`;使用 `3DTank/src/module_82347.js:177224` |
| `30786` | `ic_delete_from_group.bdff6efa.svg` | 组队成员行删除/移出图标 | 参数 `w` `3DTank/src/module_82347.js:21`;`i(30786)` `:212532`;`3DTank/src/index.json:2240-2242`;使用 `3DTank/src/module_82347.js:4765`、`:110194`(`v4.tap` 组队成员行 @`:110022`) |
## 4. 组件渲染表
只记录 `tap` 内可直接读出的层级;节点标签由 `Ry`=`div`(`3DTank/src/module_59131.js:746`)、`jx`=`span`(`3DTank/src/module_59131.js:797`)、`Iq`=`h3`(`3DTank/src/module_59131.js:758`)、`tj`=`img`(`3DTank/src/module_59131.js:770`)确认。
| 组件类/函数 | 渲染函数 tap 行号 | 生成的 DOM 结构(父 → 子) | 关键样式字段 |
|---|---|---|---|
| `x6n`(Premium 购买/入口按钮) | `3DTank/src/module_82347.js:176431` | `div(g6n)` → 可选 `div(k6n)` 文本、`div(z6n)` 高级图标、`span(b6n)` 计时器 | 根:`box-shadow`、`cursor`、`96×96`、未开通 VIP 时金红径向渐变(`176436-176441`);图标使用 `iconButtonPremium`(`176468`);state `p6n` 含 `isPremiumBuyButton` / `hasPremium` / `premiumRemainingTime`(`176382-176393`) |
| `L4`(组队标题按钮) | `3DTank/src/module_82347.js:110273` | `div($4)` → 可选 `div(j4)` 金币文本、`div(E4)` 组队槽容器 → `q4/b4` 成员槽、可选 `Z5` tooltip | `Vy/Wy 96/95`、`border-right`、背景(`110277-110283`);`UserGroupTitleButtonComponentStyle.userGroupTitle/commonBlockGroup/commonUserIconsBlockGroup`(`110307`, `110327`, `110299`);state `d4` 含 `isCanInvite` / `membersGroup` / `onlineStatus` / `maxUsersInGroup`(`110214-110225`) |
| `Eni`(用户信息子组件) | `3DTank/src/module_82347.js:177748` | `div(dni)` → `div(mni)` 等级/头像 → `img` 头像;`div(gni)` → 昵称/称号/XP 区 | `UserInfoContainerStyle`:`rankIconContainerClickable`、`containerUserNameRank`、`containerProgressMainScreen`、`xpIcon`、`progressValue`(`177761`, `177781`, `177789`, `177909-177915`);state `pni` 含 `userId` / `uid` / `userInGroup` / `userScore` / `rank` / `clanTag` / `hasPremium`(`177677-177704`) |
| `rii`(用户信息容器/左侧面板) | `3DTank/src/module_82347.js:178113` | `div(Qni)` → `x6n`、可选 `L4`、`Eni` | `UserInfoContainerStyle.blockLeftPanel`(`178116`);三个子组件均在 `isLobbyLayout` 下装配(`178117-178124`);state `Jni` 仅 `groupButtonActive`(`178104-178106`) |
| `lti`(用户货币/全屏条) | `3DTank/src/module_82347.js:176892` | `div(nti)` → 可选 `div(ati)` 临时货币、`div(sti)` 金币块、`div(iti)` 红宝石行、`div(eti)` 水晶行 | `UserScoreComponentStyle`:`temporaryCashContainer`、`coinsContainer`、`addCoins`、`blockRightPanel`、`addRubyCrystal`、`iconCrystal`、`addRubyCrystal`(`176897-176977`);state `fti` 含 `user` / `isMobileDevice` / `isFullScreen` / `showExitButton` / `removalTimer`(`177000-177014`) |
| `eKt`(全屏按钮) | `3DTank/src/module_82347.js:137348` | `div(nKt)` → `div(iKt)` 全屏图标、可选 `h3(rKt)` 文本 | `FullScreenButtonStyle.b1m0/c1m0`(`107372`, `137351-137363`);图标在 `fullscreen` / `fullscreenOff` 间切换(`137380`);state `JVt` 含 `isFullScreen` / `inputMode`(`137312-137314`) |
| `d9`(样式类,无 `tap`) | 无 `tap`(已核对为样式类) | 渲染在 `L4.tap`(`3DTank/src/module_82347.js:110273`) | `userGroupTitle`/`commonBlockGroup`/`commonUserIconsBlockGroup`(`110368-110398`) |
## 5. 未确认清单
- `x6n` / `L4` / `Eni` / `rii` / `lti` / `eKt` 的官方语义类名:未找到;`Lq` 注册对这些类均传空名(`3DTank/src/module_82347.js:107914`, `106986`, `107926`, `107929`, `107920`, `107371`),本表只用样式归属和渲染内容标注。
- `x6n` 的官方组件名:未找到;state `p6n` 已确认它是 Premium 购买/入口按钮(`isPremiumBuyButton`、`hasPremium`、`premiumRemainingTime`,`3DTank/src/module_82347.js:176382-176393`),本表按 HEAD premium 按钮记录。
- 资源模块的行号以 `3DTank/src/index.json` 的 `id` / `path` 行为准;压缩版 `main.6469b966.js` 为单行,仅用于同构对照。
已核对后移出未确认:
- `d9`:确认是 `UserGroupTitleButtonComponentStyle` 样式类,没有 `tap`;渲染在 `L4.tap`(`3DTank/src/module_82347.js:106987`, `110273`)。
- 14 个参数化/共享方法:确认不是字段访问器,已列入 1.1 节;其中 `EMi.f1s7()` 返回共享函数 `zMi`(`3DTank/src/module_82347.js:197540`, `3DTank/src/module_82347.js:90245-90258`)。
- `iconBack` / `iconBackArrow` / `back` 分工:`iconBack` 用于 Logout(`:66013`),`iconBackArrow` 用于 Breadcrumbs 子页返回(`:172388`),`back` 用于触摸控制编辑器返回(`:92464`)。
## 6. 待核对(仅记录,不改原文档)
- `3DTank/docs/HeadStyleTable.md:64` 的 `head.exit` 写 `back.72d79619.svg`;源码中 Breadcrumbs 子页返回按钮使用 `IconStyle.iconBackArrow`(`3DTank/src/module_82347.js:172388`)→ 资源模块 `98628` `iconBackArrow.9f950ca9.svg`(`3DTank/src/index.json:6635-6637`)。`back.72d79619.svg` 在 `module_82347` 中只用于触摸控制编辑器返回键(`3DTank/src/module_82347.js:92464`)。同文档 `:88` 的 `head.logout.icon` 使用 `iconBack.62869951.svg`,与源码一致;`UI_Rebuild_Plan.md:475` 描述的是 logout,也与源码一致。
- `3DTank/docs/SettingsStyleTable.md` 第 6 节存在行号漂移,语义均正确:`InputRangeComponentStyle` 的 `EUi` 构造器实际在 `3DTank/src/module_82347.js:92965`(表写 `92980`,位于首字段结束);`kUi` 实际在 `92902`(表写 `92930`);`hUi.tap` 实际在 `200482`(表写 `200507`);`SettingsComponentStyle-slider` 的 `z1jp_1 -> slider` 的 `Eq("slider")` 实际在 `89711`(表写 `89620`)。
+224
View File
@@ -0,0 +1,224 @@
# Shop Style Table
> 来源:`3DTank/src/module_82347.js`(与 `3dtank.com/play/static/js/unpacked/module_82347.js` 同构)。
> 提取方法:`3DTank/tools/headless_probe.js` 加载 `module_37036` + `module_82347`,实例化样式类,执行 `dk(...)` 的 style 函数。
> Kotlin 侧:`TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/LobbyScreens.kt` 的 `ShopScreen` 及其上方 `Shop*` 样式。
## 1. 样式类
| 类 | 语义名 | 定义 | 访问器 |
|---|---|---|---|
| `$Fi` | `NewShopCommonComponentStyle` | `93477` | `e1hz` container / `b1sk` commonBlockMenuShop / `b1k7` commonContainer / `c1sj` gradient / `l1sk` containerAnnouncement |
| `zHi` | `ShopSelectedSectionComponentStyle` | `94195` | `y1qq` commonCard / `a1sp` gradientCategory / `y1so` limitedOffersCommon / `x1so` mobileCardContainer / `c1sp` scrollContainerShop / `u1ju` commonBlock / `u1sj` activeCategory / `v1sj` notifier / `k1nd` timer;参数化 `t1sj(icon, active)` |
| `MVi` | `ShopCategoryComponentStyle` | `95731` | `e1hz` container / `ri5` header / `gax` description / `t1su` bonusContainer / `v1su` bonus / `w1su` marginItemsContainer;参数化 `u1su(rec, bonus)` |
| `YVi` | `ShopCategoryOfferSectionStyle` | `95908` | `x1su` promoCodesItemsContainer / `p1st` widthSearchInput / `b1m1` containerItem / `y1su` containerCoin / `z1su` containerLimitOffers / `a1sv` containerKitOffers |
| `RKi` | `ShopFooterComponentStyle` | `96243` | `u1hz` mainContainer / `n1sw` buttonAndTextContainer / `o1sw` exclamation / `k1sw` reportButton |
| `uXi` | `ShopItemComponentStyle` | `96891` | `k1sy` 基础卡片 / `h1sy` footerContent / `h1pt` headerContainer / `j1sy` headerScroll / `y1sx` headerContent / `i1sy` containerName / `n1sy` previewContainer / `e1mb` discount / `s1ma` discountContainer 等 |
| `PZt` | `ShopBuyButtonComponentStyle` | `31920` | `u11p` root / `vck` price |
## 2. 关键 CSS
### 2.1 左侧分类菜单
`commonBlockMenuShop`(`b1sk`):
```css
display:flex; flex-direction:column; align-items:center; justify-content:flex-start;
cursor:default; background-color:rgba(3,8,13,.25); backdrop-filter:blur(.781em);
border-radius:1rem; border-right:1.25em solid transparent;
height:max-content; margin-left:2em; margin-top:2em;
max-height:calc(100% - 9.8em); z-index:2;
padding:1.25em; padding-right:0; width:25em;
```
分类项 `t1sj(icon, active)`:
```css
display:flex; align-items:center; justify-content:flex-start;
width:23.75em; height:3.5em; min-height:3.5em; max-height:3.5em;
margin:0; position:relative; overflow:hidden; cursor:pointer;
/* active */
background-color:rgba(255,204,0,.25); border-radius:.5rem; color:rgb(255,204,0);
```
图标(`div`):
```css
width:1.5em; height:1.5em; margin-right:1.25em;
background-color:white; /* active: rgb(255,204,0) + margin-left:1.25em */
mask-image:url(icon); mask-position:center; mask-repeat:no-repeat; mask-size:contain;
```
标签(`h4`):
```css
font:500 1em/1 BaseFontMedium; text-transform:uppercase;
color:white; /* active: rgb(255,204,0) */
margin:.2em 0 0;
```
### 2.2 主容器
`commonContainer`(`b1k7`):
```css
background:radial-gradient(50% 100% at 50% 100%, #203040 0%, #03080D 100%);
display:flex; align-items:stretch; justify-content:space-between;
height:100%; width:100%; max-height:calc(100% - 6em);
```
原版 `pFi.tap` 的三个子节点:左菜单、分类内容、右侧选中区。
### 2.3 分类内容
`ShopCategoryComponentStyle-container`(`e1hz`)是 `display:contents`;标题/描述/物品网格直接进入 `commonContainer`。
标题(`ri5`):
```css
font:500 1.875em/1.4em BaseFontBold; margin:1em 0 0; text-transform:uppercase;
```
描述(`gax`):
```css
opacity:.6; line-height:1.25em; font-size:.938em;
```
物品容器 `u1su(rec, bonus)`:
```css
display:flex; flex-wrap:wrap; align-items:flex-start; justify-content:flex-start;
width:37em; height:100%; gap:1em; margin-left:.25em; margin-top:.25em; /* bonus: .6em */
```
`containerItem`(`b1m1`):
```css
display:grid; grid-template-columns:50% 50%; grid-template-rows:31.35%;
gap:1em; width:37.25em; margin:2em .9em 1em .25em; padding:0 0 0 .25em;
justify-content:flex-start; align-content:flex-start; z-index:3;
```
### 2.4 物品卡片
`k1sy`:
```css
display:flex; flex-direction:column; align-items:center; justify-content:center;
position:relative; user-select:none; box-sizing:border-box; cursor:pointer;
border-radius:1rem; background-color:rgba(255,255,255,.15);
width:17.5em; height:inherit;
box-shadow:0 0 0 1px rgba(255,255,255,.3);
```
标题容器 `h1pt`:
```css
width:calc(100% - 1.5em); position:absolute; left:.75em; top:.75em;
display:flex; align-items:center; justify-content:flex-start; z-index:1;
```
标题文字 `y1sx`:
```css
font:500 1em/1 BaseFontMedium; text-transform:uppercase; color:white; margin-top:.2em;
```
价格文字 `h1sy`:
```css
font:500 1.125em/1.125 BaseFontMedium; text-transform:uppercase; margin-top:.125em;
```
预览 `n1sy`:
```css
display:flex; align-items:center; justify-content:center;
height:auto; width:auto; max-width:80%; z-index:3;
```
折扣 `e1mb`:
```css
position:absolute; right:.625em; bottom:1em;
line-height:1.333em; font-size:.938em; font-weight:bold;
```
### 2.5 底部栏
`ShopFooterComponentStyle-mainContainer`(`u1hz`):
```css
display:flex; align-items:center; justify-content:flex-start;
width:100%; height:3.125em; position:absolute; bottom:0;
border-top:.063em solid rgba(255,255,255,.1);
backdrop-filter:blur(2.688em); z-index:3;
```
`buttonAndTextContainer`(`n1sw`):
```css
width:auto; height:2.2em; margin-left:1.8em;
display:flex; align-items:center; justify-content:flex-start;
```
`exclamation`(`o1sw`):
```css
margin-left:.7em; opacity:.5;
font:400 .875em/1 BaseFontRegular;
```
## 3. 资源
| 用途 | 资源 |
|---|---|
| 红宝石 | `ruby.ce62efbc.svg` |
| 水晶 | `crystalSmall.242e6c15.svg` |
| 金币 | `coin.3a16e831.svg` |
| 红宝石加号 | `addRuby.ff47c8be.svg` |
| 水晶加号 | `addCrystal.af9a397c.svg` |
| 金币加号 | `addCoin.cc7a25d3.svg` |
## 4. 当前实现状态
已还原:
- 左侧竖向分类菜单(25em 面板 + 23.75em × 3.5em 分类项 + 图标/激活态)
- 主容器径向渐变
- 分类标题/描述
- 2 列物品网格(37em,gap 1em)
- 物品卡片(17.5em,1rem 圆角,15% 白底,1px 内描边)
- 物品名称/价格/货币图标
- 右侧选中卡片(22.5em)
- 底部 3.125em 栏 + 购买按钮 + 价格
未展开:
- `containerItem` 的 `grid-template-rows:31.35%` 动态行高(当前固定 11.5em)
- `ShopBuyButtonComponentStyle` 的共享按钮外观(当前用金色按钮近似)
- `ShopSelectedSectionComponentStyle` 的 `commonCard` / `limitedOffersCommon` 轮播(当前是单个选中卡片)
- `ShopCategoryOfferSectionStyle` 的 `containerCoin` / `containerLimitOffers` / `containerKitOffers` 特殊分类
- 折扣/加成/礼包/促销码/能源条等子组件(当前数据模型没有这些字段)
- 分类菜单的 `(any-hover:hover)` 悬停样式(当前未加)
## 5. 验收
```powershell
node 3DTank/tools/headless_probe.js `
--url "http://localhost:8080/?autoconnect=1" `
--out "build/probe/shop" `
--width 1296 --height 1006 --wait 14000 `
--eval-file "C:\Users\TermiNexus\AppData\Local\Temp\opencode\goto_shop.js"
```
实测:
```text
shop.menu x=36 y=104 w=328 h=452
shop.category x=362 y=104 w=... h=...
shop.item 226.8 × 149.0
shop.selected 22.5em 宽
shop.footer y=966 h=40
```
+6 -1
View File
@@ -11,7 +11,7 @@
| 子页面外壳 | `LobbyView.kt` + `lobby3.css` | `ms-top` 始终显示;`#lobby-content` 从 6em 开始;不再使用独立 Breadcrumbs 顶栏 |
| 大厅首页 | `app/src/jsMain/kotlin/App/Browser/Ui/HomeScreen.kt` | Emotion;Play / 主菜单 / Footer / BattlePass / 公告 |
| 车库 | `app/src/jsMain/kotlin/App/Browser/Ui/GarageScreen.kt` | React;默认「总览」+ 右侧 4 个已装配件;分类栏、底盘/炮塔选择条、迷彩网格、左右面板、底部选择条 |
| 商店 | `app/src/jsMain/kotlin/App/Browser/Ui/LobbyScreens.kt` | 分类页签 + 物品卡片(分类/名称图标、稀有度、VIP、货币价格)+ 右侧详情/购买确认 |
| 商店 | `app/src/jsMain/kotlin/App/Browser/Ui/LobbyScreens.kt` | 已按 `NewShopCommonComponentStyle` / `ShopSelectedSectionComponentStyle` / `ShopCategoryComponentStyle` / `ShopItemComponentStyle` / `ShopFooterComponentStyle` 重建:左侧 25em 分类菜单、径向渐变主容器、分类标题/描述、2 列物品网格、17.5em 物品卡、右侧 22.5em 选中卡、底部 3.125em 购买栏;详见 `ShopStyleTable.md` |
| 幸运礼盒 | 同上 | 4 种礼盒美术、钥匙图标/数量、可能奖励稀有度、右侧详情/开启确认 |
| 战斗选择 | 同上 | 模式筛选(DM/TDM/CTF/CP/AS/JGR/SGE/RUGBY 图标)+ 战场列表 + 右侧详情/加入/创建入口 |
| 任务 | 同上 | 分类侧栏(主要/每日/每周/星级挑战/合约)+ 任务卡(进度、奖励图标、领取/去完成)+ 星级挑战横幅 + 离线示例 |
@@ -51,6 +51,11 @@
| 真实迷彩预览 | `Item3DCC.Resource` | 迷彩资源 URL 未打通;车库用色块、商店用色块渐变占位 |
| 军团/好友真实数据 | `RootState` | 已接状态与列表;真实服务器数据下会替换离线示例 |
| HEAD 组队 3/4 人分支 | `module_82347` `UserGroupTitleButtonComponentStyle-commonBlockGroup` | 2 人组(默认上限)已按 `q4` + `groupUser.svg` 还原;3/4 人组的等级小图标分支未接组队数据 |
| 商店物品卡动态行高 | `module_82347` `ShopCategoryOfferSectionStyle-containerItem` | 原版 `grid-template-rows:31.35%`,当前固定 `11.5em`;等容器高度确定后替换 |
| 商店购买按钮共享外观 | `module_82347` `ShopBuyButtonComponentStyle` | 当前用金色按钮近似;共享 Button 组件的完整样式待展开 |
| 商店选中区轮播 | `module_82347` `ShopSelectedSectionComponentStyle` | 当前单个 22.5em 选中卡;`commonCard` / `limitedOffersCommon` 的横向轮播待展开 |
| 商店特殊分类 | `module_82347` `ShopCategoryOfferSectionStyle` | `containerCoin` / `containerLimitOffers` / `containerKitOffers` 的金币、限时、礼包分类待展开 |
| 商店折扣/加成/促销码 | `module_82347` `ShopItemComponentStyle` | 当前数据模型没有折扣、加成、促销码字段;原版徽章/能源条未渲染 |
## 说明
@@ -659,6 +659,353 @@ private fun RarityColor(Rarity: Int): String = when (Rarity) {
else -> "rgba(232, 242, 255, 0.78)"
}
/** 原版 `commonBlockMenuShop`。 */
private val ShopMenu: csstype.PropertiesBuilder.() -> Unit = {
Style("display", "flex")
Style("flex-direction", "column")
Style("align-items", "center")
Style("justify-content", "flex-start")
Style("cursor", "default")
Style("background-color", "rgba(3, 8, 13, 0.25)")
Style("backdrop-filter", "blur(0.781em)")
Style("border-radius", "1rem")
Style("border-right", "1.25em solid transparent")
Style("height", "max-content")
Style("margin-left", "2em")
Style("margin-top", "2em")
Style("max-height", "calc(100% - 5.8em)")
Style("overflow-y", "auto")
Style("z-index", "2")
Style("padding", "1.25em")
Style("padding-right", "0")
Style("width", "25em")
Style("box-sizing", "border-box")
}
/** 原版 `ShopSelectedSectionComponentStyle-t1sj`(分类菜单项)。 */
private fun ShopMenuItemStyle(Active: Boolean): csstype.PropertiesBuilder.() -> Unit = {
Style("display", "flex")
Style("align-items", "center")
Style("justify-content", "flex-start")
Style("width", "23.75em")
Style("height", "3.5em")
Style("min-height", "3.5em")
Style("max-height", "3.5em")
Style("margin", "0")
Style("position", "relative")
Style("overflow", "hidden")
Style("border", "0")
Style("background", "transparent")
Style("cursor", if (Active) "default" else "pointer")
if (Active) {
Style("background-color", "rgba(255, 204, 0, 0.25)")
Style("border-radius", "0.5rem")
}
}
private fun ShopMenuIconStyle(Active: Boolean, Icon: String): csstype.PropertiesBuilder.() -> Unit = {
val Url = "url(\"${Asset("static/images/$Icon")}\")"
Style("width", "1.5em")
Style("height", "1.5em")
Style("margin-right", "1.25em")
if (Active) Style("margin-left", "1.25em")
Style("background-color", if (Active) "rgb(255, 204, 0)" else "rgb(255, 255, 255)")
Style("mask-image", Url)
Style("mask-position", "center")
Style("mask-repeat", "no-repeat")
Style("mask-size", "contain")
Style("-webkit-mask-image", Url)
Style("-webkit-mask-position", "center")
Style("-webkit-mask-repeat", "no-repeat")
Style("-webkit-mask-size", "contain")
}
private fun ShopMenuLabelStyle(Active: Boolean): csstype.PropertiesBuilder.() -> Unit = {
Style("font-family", "BaseFontMedium, FallbackFontMedium")
Style("font-style", "normal")
Style("font-weight", "500")
Style("font-size", "1em")
Style("text-transform", "uppercase")
Style("color", if (Active) "rgb(255, 204, 0)" else "rgb(255, 255, 255)")
Style("margin", "0.2em 0 0")
}
/** 原版 `NewShopCommonComponentStyle-commonContainer`。 */
private val ShopMain: csstype.PropertiesBuilder.() -> Unit = {
Style("background", "radial-gradient(50% 100% at 50% 100%, #203040 0%, #03080D 100%)")
Style("display", "flex")
Style("align-items", "stretch")
Style("justify-content", "space-between")
Style("height", "100%")
Style("width", "100%")
Style("max-height", "100%")
Style("box-sizing", "border-box")
Style("padding", "0 2em 3.125em 0")
Style("overflow", "hidden")
}
private val ShopCategory: csstype.PropertiesBuilder.() -> Unit = {
Style("display", "flex")
Style("flex-direction", "column")
Style("align-items", "flex-start")
Style("flex", "1")
Style("min-width", "0")
Style("height", "100%")
Style("padding", "1em 0 0 1em")
Style("box-sizing", "border-box")
Style("overflow", "hidden")
}
private val ShopCategoryHeader: csstype.PropertiesBuilder.() -> Unit = {
Style("font-family", "BaseFontBold, FallbackFontBold")
Style("font-style", "normal")
Style("font-weight", "500")
Style("font-size", "1.875em")
Style("line-height", "1.4em")
Style("margin", "0")
Style("text-transform", "uppercase")
}
private val ShopCategoryDesc: csstype.PropertiesBuilder.() -> Unit = {
Style("opacity", "0.6")
Style("line-height", "1.25em")
Style("font-size", "0.938em")
Style("margin", "0.25em 0 0")
}
private val ShopItemsScroll: csstype.PropertiesBuilder.() -> Unit = {
Style("flex", "1")
Style("min-height", "0")
Style("width", "100%")
Style("overflow-y", "auto")
Style("padding-right", "0.5em")
Style("box-sizing", "border-box")
}
private val ShopItems: csstype.PropertiesBuilder.() -> Unit = {
Style("display", "flex")
Style("flex-wrap", "wrap")
Style("align-content", "flex-start")
Style("justify-content", "flex-start")
Style("gap", "1em")
Style("width", "37em")
Style("margin", "0.25em 0 0 0.25em")
}
/** 原版 `ShopItemComponentStyle-k1sy`。 */
private fun ShopItemStyle(Selected: Boolean): csstype.PropertiesBuilder.() -> Unit = {
Style("display", "flex")
Style("flex-direction", "column")
Style("align-items", "center")
Style("justify-content", "center")
Style("position", "relative")
Style("user-select", "none")
Style("box-sizing", "border-box")
Style("cursor", "pointer")
Style("border", "0")
Style("border-radius", "1rem")
Style("background-color", "rgba(255, 255, 255, 0.15)")
Style("width", "17.5em")
Style("height", "11.5em")
Style(
"box-shadow",
if (Selected) "0em 0em 0em 1px rgb(255, 204, 0)" else "0em 0em 0em 1px rgba(255, 255, 255, 0.3)",
)
}
private val ShopItemHeader: csstype.PropertiesBuilder.() -> Unit = {
Style("width", "calc(100% - 1.5em)")
Style("height", "auto")
Style("position", "absolute")
Style("left", "0.75em")
Style("top", "0.75em")
Style("display", "flex")
Style("align-items", "center")
Style("justify-content", "flex-start")
Style("z-index", "1")
}
private val ShopItemName: csstype.PropertiesBuilder.() -> Unit = {
Style("font-family", "BaseFontMedium, FallbackFontMedium")
Style("font-style", "normal")
Style("font-weight", "500")
Style("font-size", "1em")
Style("text-transform", "uppercase")
Style("color", "rgb(255, 255, 255)")
Style("margin", "0.2em 0 0")
Style("white-space", "nowrap")
Style("overflow", "hidden")
Style("text-overflow", "ellipsis")
}
private val ShopItemPreview: csstype.PropertiesBuilder.() -> Unit = {
Style("display", "flex")
Style("align-items", "center")
Style("justify-content", "center")
Style("width", "6em")
Style("height", "6em")
Style("margin-top", "1.5em")
}
private val ShopItemPreviewImage: csstype.PropertiesBuilder.() -> Unit = {
Style("width", "6em")
Style("height", "6em")
Style("object-fit", "contain")
}
private val ShopItemFooter: csstype.PropertiesBuilder.() -> Unit = {
Style("position", "absolute")
Style("right", "0.75em")
Style("bottom", "0.75em")
Style("display", "flex")
Style("align-items", "center")
Style("gap", "0.25em")
Style("z-index", "2")
}
private val ShopItemPrice: csstype.PropertiesBuilder.() -> Unit = {
Style("font-family", "BaseFontMedium, FallbackFontMedium")
Style("font-style", "normal")
Style("font-weight", "500")
Style("font-size", "1.125em")
Style("text-transform", "uppercase")
Style("margin-top", "0.125em")
}
private val ShopItemCurrency: csstype.PropertiesBuilder.() -> Unit = {
Style("width", "1em")
Style("height", "1em")
}
private val ShopSelected: csstype.PropertiesBuilder.() -> Unit = {
Style("display", "flex")
Style("flex-direction", "column")
Style("align-items", "stretch")
Style("width", "22.5em")
Style("flex", "0 0 22.5em")
Style("align-self", "flex-start")
Style("margin-top", "2em")
Style("padding", "0.4em")
Style("border-radius", "0.625em")
Style("background", "rgba(255, 255, 255, 0.08)")
Style("box-sizing", "border-box")
Style("overflow", "hidden")
}
private val ShopSelectedPreview: csstype.PropertiesBuilder.() -> Unit = {
Style("display", "flex")
Style("align-items", "center")
Style("justify-content", "center")
Style("height", "12em")
Style("border-radius", "0.4em")
Style("background-position", "center")
Style("background-repeat", "no-repeat")
Style("background-size", "contain")
Style("background-color", "rgba(0, 12, 20, 0.35)")
}
private val ShopSelectedBody: csstype.PropertiesBuilder.() -> Unit = {
Style("padding", "0.75em 0.6em 0.6em")
}
private val ShopSelectedTitle: csstype.PropertiesBuilder.() -> Unit = {
Style("font-family", "BaseFontMedium, FallbackFontMedium")
Style("font-style", "normal")
Style("font-weight", "500")
Style("font-size", "1.125em")
Style("line-height", "1.2")
Style("text-transform", "uppercase")
Style("margin", "0")
}
private val ShopSelectedMeta: csstype.PropertiesBuilder.() -> Unit = {
Style("display", "flex")
Style("align-items", "center")
Style("gap", "0.6em")
Style("margin-top", "0.35em")
Style("font-size", "0.875em")
Style("opacity", "0.7")
}
private val ShopSelectedPrice: csstype.PropertiesBuilder.() -> Unit = {
Style("display", "flex")
Style("align-items", "center")
Style("gap", "0.4em")
Style("margin-top", "0.75em")
}
/** 原版 `ShopFooterComponentStyle-mainContainer`。 */
private val ShopFooter: csstype.PropertiesBuilder.() -> Unit = {
Style("display", "flex")
Style("align-items", "center")
Style("justify-content", "flex-start")
Style("width", "100%")
Style("height", "3.125em")
Style("position", "absolute")
Style("bottom", "0")
Style("left", "0")
Style("border-top", "0.063em solid rgba(255, 255, 255, 0.1)")
Style("backdrop-filter", "blur(2.688em)")
Style("background", "rgba(3, 8, 13, 0.25)")
Style("z-index", "3")
}
private val ShopFooterInner: csstype.PropertiesBuilder.() -> Unit = {
Style("width", "auto")
Style("height", "2.2em")
Style("margin-left", "1.8em")
Style("display", "flex")
Style("align-items", "center")
Style("justify-content", "flex-start")
}
private val ShopFooterHint: csstype.PropertiesBuilder.() -> Unit = {
Style("margin-left", "0.7em")
Style("opacity", "0.5")
Style("font-family", "BaseFontRegular, FallbackFontRegular")
Style("font-style", "normal")
Style("font-weight", "normal")
Style("font-size", "0.875em")
}
/** 原版 `ShopBuyButtonComponentStyle`:深色文字 + 金色按钮。 */
private val ShopBuyButton: csstype.PropertiesBuilder.() -> Unit = {
Style("display", "inline-flex")
Style("align-items", "center")
Style("justify-content", "center")
Style("height", "1.5em")
Style("padding", "0 0.8em")
Style("border", "0")
Style("border-radius", "0.25em")
Style("background", "rgb(255, 204, 0)")
Style("color", "rgb(0, 25, 38)")
Style("font", "500 1em/1 BaseFontMedium, FallbackFontMedium")
Style("cursor", "pointer")
}
private val ShopBuyPrice: csstype.PropertiesBuilder.() -> Unit = {
Style("display", "flex")
Style("align-items", "center")
Style("justify-content", "flex-end")
Style("min-width", "3em")
Style("min-height", "1.5em")
Style("margin-left", "0.5em")
}
private fun ShopItemCurrencyAsset(Currency: Int): String = when (Currency) {
0 -> "crystalSmall.242e6c15.svg"
1 -> "coin.3a16e831.svg"
2 -> "ruby.ce62efbc.svg"
else -> "coin.3a16e831.svg"
}
private fun ShopItemCurrencyColor(Currency: Int): String = when (Currency) {
0 -> Palette.Cyan
1 -> Palette.Yellow
2 -> Palette.Red
else -> Palette.White
}
private val ShopScreen = FC<Props> {
var Category by useState("all")
var SelectedId by useState<Long?>(null)
@@ -666,86 +1013,96 @@ private val ShopScreen = FC<Props> {
val Shown = if (Category == "all") Items else Items.filter { it.Category.toString() == Category }
val Categories = GarageCategories.filter { C -> Items.any { it.Category == C.Id } }
val Selected = Shown.firstOrNull { it.Id == SelectedId } ?: Shown.firstOrNull()
val ActiveLabel = Categories.firstOrNull { it.Id.toString() == Category }?.Label ?: "全部"
ScreenShell("商店", "${Items.size} 件可购买") {
div {
css(TabBar)
button {
css(TabStyle(Category == "all"))
onClick = { Category = "all"; SelectedId = null }
+"全部"
}
for (C in Categories) {
button {
css(TabStyle(Category == C.Id.toString()))
onClick = { Category = C.Id.toString(); SelectedId = null }
+C.Label
}
}
}
div {
css(ScreenRoot)
Ui("screen.商店")
// 原版 commonContainer:左菜单 / 中间物品 / 右侧选中
div {
css(Columns)
css(ShopMain)
Ui("shop.main")
div {
css {
Style("flex", "1")
Style("min-width", "0")
css(ShopMenu)
Ui("shop.menu")
button {
css(ShopMenuItemStyle(Category == "all"))
Ui("shop.menu.all")
onClick = { Category = "all"; SelectedId = null }
div { css(ShopMenuIconStyle(Category == "all", "iconLobbyMenuContainers.f960290f.svg")) }
span { css(ShopMenuLabelStyle(Category == "all")); +"全部" }
}
if (Shown.isEmpty()) {
EmptyState("离线目录里暂无可购买物品。")
} else {
div {
css(Grid)
for (Item in Shown.take(120)) {
button {
val CardBase = CardStyle(Item.Id == Selected?.Id)
css {
CardBase(this)
Style("padding", "0")
}
onClick = { SelectedId = Item.Id }
div {
css {
Style("height", "5.5em")
Style("border-radius", "0.45em 0.45em 0 0")
if (Item.Category == 2) {
Style("background", PaintGradient(Item.Name))
} else {
Style("background", "url(\"${Asset("static/images/" + ItemIcon(Item))}\") center / contain no-repeat")
Style("background-color", "rgba(0, 12, 20, 0.5)")
}
Style("border-bottom", "1px solid ${Ui.DividerSoft}")
Style("opacity", "0.92")
}
}
div {
css { Style("padding", "0.65em 0.8em 0.8em") }
for (C in Categories) {
val Active = Category == C.Id.toString()
button {
css(ShopMenuItemStyle(Active))
Ui("shop.menu.${C.Id}")
onClick = { Category = C.Id.toString(); SelectedId = null }
div { css(ShopMenuIconStyle(Active, CategoryIcon(C.Id))) }
span { css(ShopMenuLabelStyle(Active)); +C.Label }
}
}
}
div {
css(ShopCategory)
Ui("shop.category")
h2 { css(ShopCategoryHeader); Ui("shop.category.header"); +ActiveLabel }
p {
css(ShopCategoryDesc)
Ui("shop.category.desc")
+"${Shown.size} 件物品 · 选择物品查看详情"
}
div {
css(ShopItemsScroll)
Ui("shop.items.scroll")
if (Shown.isEmpty()) {
EmptyState("离线目录里暂无可购买物品。")
} else {
div {
css(ShopItems)
Ui("shop.items")
for (Item in Shown.take(200)) {
button {
css(ShopItemStyle(Item.Id == Selected?.Id))
Ui("shop.item.${Item.Id}")
onClick = { SelectedId = Item.Id }
div {
css {
Style("display", "flex")
Style("align-items", "center")
Style("justify-content", "space-between")
Style("gap", "0.4em")
}
div { css(CardTitle); +Item.Name }
if (Item.Premium) Pill("VIP", "#ffcc00")
css(ShopItemHeader)
span { css(ShopItemName); +Item.Name }
}
if (Item.Category == 2) {
div {
css {
Apply(ShopItemPreview)
Style("background", PaintGradient(Item.Name))
Style("border-radius", "0.5em")
}
}
} else {
div {
css(ShopItemPreview)
img {
src = Asset("static/images/" + ItemIcon(Item))
css(ShopItemPreviewImage)
}
}
}
div { css(CardText); +CategoryLabel(Item.Category) }
div {
css {
Style("display", "flex")
Style("align-items", "center")
Style("justify-content", "space-between")
Style("margin-top", "0.55em")
css(ShopItemFooter)
img {
src = Asset("static/images/" + ShopItemCurrencyAsset(Item.Currency))
css(ShopItemCurrency)
}
span {
css {
Style("color", RarityColor(Item.Rarity))
Style("font", "500 0.78em/1 ${Ui.FontMedium}")
Apply(ShopItemPrice)
Style("color", ShopItemCurrencyColor(Item.Currency))
}
+RarityName(Item.Rarity)
+"${Item.Price}"
}
span { css { Style("color", Ui.Cyan); Style("font", "500 0.9em/1 ${Ui.FontMedium}") }; +PriceText(Item) }
}
}
}
@@ -755,112 +1112,126 @@ private val ShopScreen = FC<Props> {
}
div {
css {
Style("width", "22.5em")
Style("flex", "0 0 22.5em")
Style("align-self", "flex-start")
}
css {
Apply(Panel)
Style("overflow", "hidden") }
css(ShopSelected)
Ui("shop.selected")
if (Selected == null) {
div {
css {
Style("padding", "1.2em")
Style("color", Ui.Muted)
Style("color", Palette.TextMuted)
}
+"选择一件物品查看详情。"
}
} else {
div {
css {
Style("height", "11em")
if (Selected.Category == 2) {
if (Selected.Category == 2) {
div {
css {
Apply(ShopSelectedPreview)
Style("background", PaintGradient(Selected.Name))
} else {
Style("background", "url(\"${Asset("static/images/" + ItemIcon(Selected))}\") center / contain no-repeat")
Style("background-color", "rgba(0, 12, 20, 0.55)")
}
Style("border-bottom", "1px solid ${Ui.DividerSoft}")
}
} else {
div {
css(ShopSelectedPreview)
img {
src = Asset("static/images/" + ItemIcon(Selected))
css {
Style("width", "12em")
Style("height", "12em")
Style("object-fit", "contain")
}
}
}
}
div {
css { Style("padding", "1em 1.1em 1.2em") }
css(ShopSelectedBody)
h3 { css(ShopSelectedTitle); +Selected.Name }
div {
css {
Style("display", "flex")
Style("align-items", "center")
Style("justify-content", "space-between")
Style("gap", "0.5em")
}
h2 { css(CardTitle); +Selected.Name }
if (Selected.Premium) Pill("VIP", "#ffcc00")
css(ShopSelectedMeta)
span { +CategoryLabel(Selected.Category) }
span { +RarityName(Selected.Rarity) }
}
div {
css {
Style("display", "flex")
Style("align-items", "center")
Style("gap", "0.6em")
Style("margin-top", "0.25em")
}
span { css(CardText); +CategoryLabel(Selected.Category) }
span { css { Style("color", RarityColor(Selected.Rarity)); Style("font", "500 0.8em/1 ${Ui.FontMedium}") }; +RarityName(Selected.Rarity) }
}
div {
css {
Style("display", "flex")
Style("align-items", "center")
Style("gap", "0.5em")
Style("margin-top", "0.8em")
}
css(ShopSelectedPrice)
img {
src = Asset(
"static/images/" + when (Selected.Currency) {
0 -> "crystalSmall.242e6c15.svg"
1 -> "coin.3a16e831.svg"
2 -> "ruby.ce62efbc.svg"
else -> "coin.3a16e831.svg"
}
)
src = Asset("static/images/" + ShopItemCurrencyAsset(Selected.Currency))
css { Style("width", "1.4em"); Style("height", "1.4em") }
}
span { css { Style("color", Ui.Cyan); Style("font", "500 1.2em/1 ${Ui.FontMedium}") }; +PriceText(Selected) }
}
p {
css {
Style("margin-top", "0.8em")
Style("color", Ui.Muted)
Style("font", "400 0.88em/1.5 ${Ui.FontRegular}")
}
+"购买后可在车库中装备。离线模式下只演示购买确认流程。"
}
div {
css { Style("display", "flex"); Style("gap", "0.5em"); Style("margin-top", "0.8em") }
button {
css(PrimaryButton)
onClick = {
DialogContext.Show(
"购买确认",
"确定购买「${Selected.Name}」吗?\n价格:${PriceText(Selected)}",
"购买",
"取消",
OnConfirm = {
ToastContext.Show("已提交购买:${Selected.Name}(离线模式)")
},
)
span {
css {
Style("font", "500 1.2em/1 BaseFontMedium, FallbackFontMedium")
Style("color", ShopItemCurrencyColor(Selected.Currency))
}
+"购买"
+PriceText(Selected)
}
button {
css(SecondaryButton)
onClick = { ScreenContext.OnNavigate("garage") }
+"去车库"
}
button {
css {
Apply(ShopBuyButton)
Style("margin-top", "0.9em")
}
Ui("shop.selected.buy")
onClick = {
DialogContext.Show(
"购买确认",
"确定购买「${Selected.Name}」吗?\n价格:${PriceText(Selected)}",
"购买",
"取消",
OnConfirm = {
ToastContext.Show("已提交购买:${Selected.Name}(离线模式)")
},
)
}
+"购买"
}
}
}
}
}
// 原版 ShopFooterComponentStyle
div {
css(ShopFooter)
Ui("shop.footer")
div {
css(ShopFooterInner)
button {
css(ShopBuyButton)
Ui("shop.footer.buy")
onClick = {
Selected?.let { Item ->
DialogContext.Show(
"购买确认",
"确定购买「${Item.Name}」吗?\n价格:${PriceText(Item)}",
"购买",
"取消",
OnConfirm = {
ToastContext.Show("已提交购买:${Item.Name}(离线模式)")
},
)
}
}
+"购买"
}
Selected?.let { Item ->
div {
css(ShopBuyPrice)
img {
src = Asset("static/images/" + ShopItemCurrencyAsset(Item.Currency))
css(ShopItemCurrency)
}
span {
css {
Style("font", "500 1em/1 BaseFontMedium, FallbackFontMedium")
Style("color", ShopItemCurrencyColor(Item.Currency))
}
+"${Item.Price}"
}
}
}
span { css(ShopFooterHint); +"选择物品后可购买" }
}
}
}
}