feedback7 R22-LOADOUT: 干员调配's roster is one list — a row per operator with the quick skill / module choices and the 潜能 / 练度 selects in aligned columns — and the detail opens with 潜能与练度; golden: unchanged
The owner's decisions of 2026-10-08: 「D12 [PR #301] 可以做,具体布局怎么优化你来决定,他那样确实方便,但是三个竖排有点乱影响 观感」 and 「D7D8调配干员里自己设置吧,默认满潜满加成」. The quick choices are PR #301's idea (by @farridge); the layout is new: one list instead of three columns of cards. - screens/loadout.js: RosterHead + RosterRow — every row the same four columns under a sticky head (干员 | 技能 | 模组 精锐 | 潜能 · 练度): portrait / name / bonds (selects, opens the detail), three skill slots (an empty one for a two-skill chess; the label says slot, name, SP, duration — quickSkillTags), the elite's modules (不装备 first), the 潜能 / 练度 selects; a quick choice goes through setChoice / setEntries like the detail and keeps a phone on the list. The detail's 普通 / 精锐 skill toggle is one preview for the skills, the 特质 and the rows. The detail opens with the 潜能与练度 section; 恢复默认 / 全部恢复默认 reset the settings too (全部: the roster's operators — a 自选 pick's stay with the 自选编队 tab); 导入 reports a settings-only import; ←/→ stay inside a focused row's choices. - screens/cultivation.js (new): CultivationSelects (two compact selects; quiet at the defaults, mint when changed) and CultivationSection (潜能 1–6 and the four official tiers — effects.json names and effect texts — the rule in one line, the stand-in note for a chess marked not owned). - screens/diy.js: an owned 自选 pick's slot card carries its operator's selects; a prototype says it has neither. - css: the head and the rows share one grid (`--cols`), 32 px quick targets (40 px on a coarse pointer); the detail slides over the list below 1000 px (was 760 px); below 700 px the row's bonds give way to the name. Headless check at 1920 × 1080, 1366 × 768, 844 × 390 and 667 × 375: no sideways overflow, the head's columns equal every row's. - The 局内数值 caption says 含潜能与练度 when the numbers carry both (effects.json loaded); the overlay's note says the skills, modules, 潜能 and 练度 can be set (the operator's in-match level cannot). - i18n: 21 strings in the four packs (the tier names, short tier labels, the notes, the reset confirm, the import toast). - Docs: design/match.md (the list, the 潜能与练度 section, 导出's `ops`), ARCHITECTURE.md (screens/cultivation.js). - Tests: test/ui/loadout-rows.test.js (new, 7): a row's pick / skill slots / modules / aria-pressed / callbacks and the shared 精锐 preview, its selects (values, options, a change, the changed mark, a stand-in), the head, the detail's section (tiers from effects.json, the effect text, the stand-in note), a 自选 owned pick vs a prototype, 局内数值 at 潜能1 / 未精英化 and at the defaults with its caption, the css grid and breakpoints. e2e (not run here; their steps replayed with a headless page): the row pick selector, the phone test (one list, 40 px targets, the detail slides over, back to the list), the 局内数值 expectations ×1.1, a new 潜能 / 练度 flow (row select → stored, 部署费用 17 / 攻击 454; the detail's 未精英化 → 413; the row follows; 恢复默认).
This commit is contained in:
1 parent
519f04e2d5
commit
dc336f24a1
14 files changed
+673
-149
No files matched your search
@@ -6,7 +6,8 @@
|
||||
// 原型 / 已持有 tag —, an operator held by another slot greyed out with where it is. In the match the operator is sold
|
||||
// only in this player's shop, from 调度中心 level 5 (tier 5) / 6 (tier 6), and fights with the pick (server/match/player
|
||||
// /diy.js). Out of match: the next match takes the picks. The picks live in ui/loadoutSync.js (localStorage + room.diy);
|
||||
// the model is ui/diyModel.js. Styles: css/screens/loadout.css (diy-*).
|
||||
// the model is ui/diyModel.js. Styles: css/screens/loadout.css (diy-*). An owned pick's card carries its operator's 潜能 /
|
||||
// 练度 selects (0.2.2, screens/cultivation.js — the same per-operator settings as 干员调配, `ops`); a prototype has neither.
|
||||
|
||||
import { useEffect, useState } from '../../vendor/hooks.module.js';
|
||||
import { html, Icon, Button, TierChip } from '../ui/components.js';
|
||||
@@ -15,6 +16,7 @@ import { chessAvatarUrl, chessPortraitUrl, profIconUrl, skillRecordIconUrl, modu
|
||||
import { data } from '../data.js';
|
||||
import { PROF_NAME, skillLabel, moduleBadge, fullTraitText } from '../ui/loadoutModel.js';
|
||||
import { diySlotList, pickChoices, pickOptions, slotRecord, defaultPick } from '../ui/diyModel.js';
|
||||
import { CultivationSelects } from './cultivation.js';
|
||||
import { t } from '../../../shared/i18n.js';
|
||||
|
||||
const cx = (...p) => p.flat().filter(Boolean).join(' ');
|
||||
@@ -38,8 +40,8 @@ function Bonds({ bonds }) {
|
||||
return html`<span class="diy-bonds">${(bonds || []).map((b) => html`<span key=${b} class="diy-bond"><${BondGlyph} bondId=${b} /><span>${bondName(b)}</span></span>`)}</span>`;
|
||||
}
|
||||
|
||||
/** One slot: the pick (operator, class, bonds, skill, module) or an empty slot to fill. */
|
||||
function SlotCard({ m, slot, pick, illegal, onOpen, onClear }) {
|
||||
/** One slot: the pick (operator, class, bonds, skill, module, an owned pick's 潜能 / 练度) or an empty slot to fill. */
|
||||
function SlotCard({ m, slot, pick, illegal, onOpen, onClear, ops = {}, onOps = null }) {
|
||||
const D = diyData();
|
||||
const rec = pick ? slotRecord(slot.slotId, pick, D) : null;
|
||||
const elite = pick ? slotRecord(slot.slotId, pick, D, { elite: true }) : null;
|
||||
@@ -73,6 +75,8 @@ function SlotCard({ m, slot, pick, illegal, onOpen, onClear }) {
|
||||
${mod ? html`<${Img} src=${moduleTypeIconUrl(data.get('local'), mod.type)} class="diy-slot__micon" fallback=${html`<b class="num">${moduleBadge({ typeName: mod.type })}</b>`} />` : html`<span class="diy-slot__micon diy-slot__micon--none">—</span>`}
|
||||
<span>${mod ? html`<b class="num">${mod.type || ''}</b> ${mod.name || ''}` : t('不装备模组')} <small class="t-dim">${t('(精锐时生效)')}</small></span>
|
||||
</span>
|
||||
${proto ? html`<small class="diy-slot__cult t-dim">${t('原型干员没有潜能与练度')}</small>`
|
||||
: onOps ? html`<span class="diy-slot__cult"><${CultivationSelects} charId=${rec.charId} ops=${ops} onSet=${onOps} /></span>` : null}
|
||||
${illegal ? html`<p class="diy-slot__bad"><${Icon} name="warn" />${t('这项自选在当前版本不可用,开局时会被移除')}</p>` : null}
|
||||
</div>
|
||||
<div class="diy-slot__acts">
|
||||
@@ -226,7 +230,9 @@ export function DiyPickerView({ m, slot, picks, kitted, onDone, onClose, filter,
|
||||
/**
|
||||
* The tab's body: the four slots by tier, and the picker of the slot being filled (`picking`, kept here).
|
||||
* @param {{ m: any, picks: Record<string, any>, legal: Record<string, any>, kitted: string[]|null,
|
||||
* onSet: (slotId: string, pick: any) => void }} props
|
||||
* onSet: (slotId: string, pick: any) => void, ops?: Record<string, any>,
|
||||
* onOps?: ((charId: string, patch: { potential?: number, cultivate?: number }) => void) | null }} props
|
||||
* `ops` / `onOps`: the stored 潜能 / 练度 and their setter (0.2.2 — an owned pick's card shows its selects)
|
||||
*/
|
||||
export function DiyPanel(props) {
|
||||
const [picking, setPicking] = useState(null);
|
||||
@@ -234,7 +240,7 @@ export function DiyPanel(props) {
|
||||
}
|
||||
|
||||
/** The tab's view (no hooks: the tests draw it): `picking` = the slot being filled, or null. */
|
||||
export function DiyPanelView({ m, picks, legal, kitted, onSet, picking, onPicking: setPicking }) {
|
||||
export function DiyPanelView({ m, picks, legal, kitted, onSet, picking, onPicking: setPicking, ops = {}, onOps = null }) {
|
||||
const slots = diySlotList(diyData());
|
||||
const slot = picking ? slots.find((s) => s.slotId === picking) : null;
|
||||
const tiers = [...new Set(slots.map((s) => s.tier))];
|
||||
@@ -247,7 +253,7 @@ export function DiyPanelView({ m, picks, legal, kitted, onSet, picking, onPickin
|
||||
<h3 class="own-tier__head"><${TierChip} tier=${tier} size="sm" /><span class="num">${ROMAN[tier]}</span><span>${t('阶')}</span></h3>
|
||||
<div class="diy-grid">
|
||||
${slots.filter((s) => s.tier === tier).map((s) => html`<${SlotCard} key=${s.slotId} m=${m} slot=${s} pick=${picks[s.slotId] || null}
|
||||
illegal=${!!picks[s.slotId] && !legal[s.slotId]} onOpen=${setPicking} onClear=${(id) => onSet(id, null)} />`)}
|
||||
illegal=${!!picks[s.slotId] && !legal[s.slotId]} onOpen=${setPicking} onClear=${(id) => onSet(id, null)} ops=${ops} onOps=${onOps} />`)}
|
||||
</div>
|
||||
</section>`)}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user