feedback7 R22-LOADOUT: 干员调配's detail starts with the operator's 特质 (the in-match card's block), and the compact skill tags of the quick choices; golden: unchanged
Substantially PR #301 by @farridge (its non-layout part, ported; the owner's decision of 2026-10-08: 「可以做,具体布局 怎么优化你来决定」 — the card layout is redesigned in the next commit): - ui/detailPanel.js exports GarrisonBlock; screens/loadout.js LoadoutGarrisons renders the 特质 of the form the detail's 普通 / 精锐 skill toggle shows (garrisons.json `descRaw` with its trigger markers, 「<进入休整期时>」 …) at the top of the detail; garrisons.json loads with the overlay; its reading sizes in loadout.css. - ui/loadoutModel.js quickSkillTags: SP recovery (攻回 / 受回 / 自回 / 被动, 部署触发 for a deploy-and-expire passive) and duration (s, 弹药, 常驻, ∞, 瞬发) from the generated SkillRecord, never from a localized description. - i18n: the six new strings in the four packs (en from the PR; ja / ko / zh-TW added). - Tests: test/ui/loadout-quick.test.js (from the PR, 4): quick tags of 空弦 / deployment skills / ammo / infinite, the 特质 blocks of 空弦 / 星熊 / 缄默德克萨斯 both forms; loadout-stats.test.js reads the new import.
This commit is contained in:
1 parent
7a0937e3dd
commit
519f04e2d5
12 files changed
+142
-9
No files matched your search
@@ -22,7 +22,7 @@ import { useEffect, useMemo, useRef, useState } from '../../vendor/hooks.module.
|
||||
import { html, Icon, MicroLabel, Button, TierChip, TextField, Countdown, Spinner, confirmDialog, hasDeadline, Modal, Fragment } from '../ui/components.js';
|
||||
import { Img, RichText, UnitThumb } from '../ui/gameComponents.js';
|
||||
import { chessAvatarUrl, chessPortraitUrl, subProfIconUrl, bondIconUrl, moduleTypeIconUrl } from '../ui/assetUrls.js';
|
||||
import { chessStatsBlock, traitText, chessTalents } from '../ui/detailPanel.js';
|
||||
import { chessStatsBlock, traitText, chessTalents, GarrisonBlock } from '../ui/detailPanel.js';
|
||||
import { chessLoadout } from '../ui/gameLogic.js';
|
||||
import { data, useData, localAsset, DATA_FILES } from '../data.js';
|
||||
import { useStore } from '../store.js';
|
||||
@@ -172,6 +172,14 @@ function RosterCard({ m, chess, golden, entries, selected, onPick, notOwned = fa
|
||||
|
||||
// ---- detail --------------------------------------------------------------------------------------------------------------
|
||||
|
||||
/** The same attribute records and rich-text renderer as the in-match card, including literal trigger markers. */
|
||||
export function LoadoutGarrisons({ chess, m }) {
|
||||
const records = (chess?.garrisonIds || []).map((id) => data.lookup('garrisons', id)).filter(Boolean);
|
||||
return records.length ? html`<section class="lo-garrisons" data-variant=${chess.isGolden ? 'elite' : 'normal'}>
|
||||
${records.map((garrison) => html`<${GarrisonBlock} key=${garrison.garrisonId} garrison=${garrison} m=${m} />`)}
|
||||
</section>` : null;
|
||||
}
|
||||
|
||||
function SkillOption({ m, opt, on, level, onPick }) {
|
||||
const rec = level === 'elite' ? opt.elite || opt.normal : opt.normal || opt.elite;
|
||||
const tags = skillTags(rec);
|
||||
@@ -310,6 +318,7 @@ function Detail({ m, chess, golden, entries, onChange, onReset, locked, notOwned
|
||||
<${Button} variant="ghost" size="sm" icon="refresh" class="lo-dhead__reset" disabled=${!choice.changed} onClick=${onReset}>${t('恢复默认')}<//>
|
||||
</div>
|
||||
<div class="lo-detail__body" ref=${bodyRef}>
|
||||
<${LoadoutGarrisons} chess=${level === 'elite' && golden ? golden : chess} m=${m} />
|
||||
<section class="lo-sec">
|
||||
<header class="lo-sec__head">
|
||||
<h3>${t('技能')}<${MicroLabel}>SKILL<//></h3>
|
||||
@@ -433,7 +442,7 @@ export function DataMissing({ files }) {
|
||||
|
||||
/** The overlay screen. */
|
||||
function LoadoutScreen({ st }) {
|
||||
const ready = useData('chess', 'bonds', 'assets', 'local', 'backups');
|
||||
const ready = useData('chess', 'bonds', 'assets', 'local', 'backups', 'garrisons');
|
||||
const phase = useStore((s) => s.match?.public?.phase || null);
|
||||
const inMatch = useStore((s) => !!s.room?.inMatch);
|
||||
// co-op briefing (INFO_CHECK, 25 s): the overlay covers the briefing's own countdown, so it shows the time left — the
|
||||
|
||||
@@ -315,7 +315,7 @@ export function garrisonTypeIconKey(garrison) {
|
||||
}
|
||||
|
||||
/** The operator's own effect (特质, garrisons.json): trigger chip + description, compact. */
|
||||
function GarrisonBlock({ garrison, m }) {
|
||||
export function GarrisonBlock({ garrison, m }) {
|
||||
return html`<section class="dgarrison" aria-label=${t('特质')} data-garrison=${garrison.garrisonId || ''}>
|
||||
<div class="dgarrison__head">
|
||||
<span class="dgarrison__k">${t('特质')}</span>
|
||||
|
||||
@@ -488,3 +488,17 @@ export function skillTags(rec) {
|
||||
passive,
|
||||
};
|
||||
}
|
||||
|
||||
/** Compact skill preview; SP and duration come from the selected form's generated record. */
|
||||
export function quickSkillTags(rec) {
|
||||
const tags = skillTags(rec);
|
||||
if (!rec) return { ...tags, recovery: '—', duration: '—' };
|
||||
// ON_DEPLOY covers constant passives too. A finite duration distinguishes the deploy-and-expire skills;
|
||||
// some older records carry it only in the blackboard. Never infer it from a localized description.
|
||||
const seconds = Number(rec.duration) > 0 ? Number(rec.duration) : tags.passive ? Number(rec.bb?.duration) : 0;
|
||||
const deployment = tags.passive && seconds > 0;
|
||||
const recovery = deployment ? '—' : tags.passive ? t('被动') : tags.spKind === 'atk' ? t('攻回') : tags.spKind === 'def' ? t('受回') : t('自回');
|
||||
const duration = rec.durationType === 'AMMO' ? t('弹药') : seconds > 0 ? `${seconds}s`
|
||||
: tags.passive ? t('常驻') : Number(rec.duration) < 0 ? '∞' : t('瞬发');
|
||||
return { ...tags, sp: deployment ? t('部署触发') : tags.sp, recovery, duration };
|
||||
}
|
||||
@@ -152,6 +152,7 @@ export function openLoadout(from = 'lobby', sel = null, tab = null) {
|
||||
data.load('local');
|
||||
data.load('backups');
|
||||
data.load('effects'); // 0.2.2: the 练度 multipliers of the 局内数值 (effects.json CHAR_MAP)
|
||||
data.load('garrisons'); // the 特质 at the top of the detail (PR #301)
|
||||
loadoutStore.set({ open: true, from, ...(sel ? { sel } : {}), ...(tab === 'loadout' || tab === 'ownership' || tab === 'diy' ? { tab } : {}) });
|
||||
}
|
||||
export const closeLoadout = () => loadoutStore.set({ open: false });
|
||||
|
||||
Reference in new issue
Block a user