v0.1.11: 全源码审计 + 缺陷歼灭 + UI/UX 打磨

【引擎审计修复】
- P0:大比 once(原整年刷 12 场、奖励通胀);flag 年份键 3 年剪枝(长线膨胀)
- P1:飞升离世累加 feishengCount(飞升之资称号可达);圣者护族接消费(原死代码);
  大比/传薪挂能力卡门控;pending 抢渡劫时不再重设
- P2:version 硬编码统一 0.1.11;yearaxis 飞升归类;MonumentList 挂载

【UI 审计修复】
- P0:已故成员卡显示「已殁」(原秀活动 status);空队迎战禁用+提示(原静默失效)
- P1:指婚候选去截断(滚动列表);终局出口(回卷/主菜单按钮);saveNow 反馈;
  事件来临写入 feed(k-event 使用);遣队「最强 N 人/清空」一键;寻衅确认+联姻原因提示;
  未建建筑视觉 uc 类
- P2:speed 死代码;Modal 分层 z-index;FamilyPanel 在世/先人分隔

【UX 三件套】
- 警讯徽章(瓶颈/重伤/在外/媒缘/孤嗣之忧)顶栏一屏秒答「我该干嘛」
- 首启引导任务条(四步目标 + 完成推进,可跳不强制)
- 顶栏丹药瘦身(4 资源);数字千/万化;瓶颈卡红框高亮

【测试】905→923(urgency/guide/format 三模块 + 审计修复回归 6 项)
金钟罩三档重固化(0.1.11),typecheck/build 通过
This commit is contained in:
2026-08-23 11:30:16 +08:00
parent 586fa643b1
commit 9bcb5d6af8
25 changed files with 417 additions and 28 deletions
+29
View File
@@ -0,0 +1,29 @@
import { useGameStore } from '../store'
import { GUIDE_STEPS, computeGuide, guideAdvance } from '../../game/core/guide'
export function GuideStrip() {
const world = useGameStore((s) => s.world)
const revision = useGameStore((s) => s.revision)
const bump = useGameStore((s) => s.bump)
void revision
if (!world) return null
const g = computeGuide(world)
if (g.done || world.state.year > 2) return null
const step = GUIDE_STEPS[g.step]
if (!step) return null
const advance = () => {
guideAdvance(world, g.step + 1)
bump()
}
return (
<div className="guide-strip">
<span className="guide-icon"></span>
<div style={{ flex: 1 }}>
<b>{step.title}</b>
<span className="dim" style={{ marginLeft: 8 }}>{step.hint}</span>
</div>
<button className="btn btn-sm btn-primary" onClick={advance}></button>
<button className="btn btn-sm" onClick={advance} title="跳过引导">×</button>
</div>
)
}
+6 -4
View File
@@ -9,7 +9,8 @@ const STATE_LABEL: Record<string, { label: string; cls: string }> = {
expedition: { label: '出探', cls: 'warn' },
wounded: { label: '养伤', cls: 'bad' },
apprentice: { label: '寄读', cls: 'gold' },
closed: { label: '养息', cls: '' }
closed: { label: '养息', cls: '' },
dead: { label: '已殁', cls: 'bad' }
}
export function MemberCard({ c }: { c: Character }) {
@@ -18,9 +19,10 @@ export function MemberCard({ c }: { c: Character }) {
if (!world) return null
const dead = !c.alive
const isHead = world.state.family.headId === c.id
const state = STATE_LABEL[c.state]
const state = STATE_LABEL[dead ? 'dead' : c.state]
const blocked = !dead && c.realmProgress >= 100 && c.realm.major !== 'spirit'
return (
<div className={`member-card ${dead ? 'dead' : ''}`} onClick={() => setSelected(c.id)}>
<div className={`member-card ${dead ? 'dead' : ''} ${blocked ? 'blocked' : ''}`} onClick={() => setSelected(c.id)}>
<div className="mc-head">
<div className="m-avatar">{world.state.family.surname}</div>
<div style={{ flex: 1 }}>
@@ -40,7 +42,7 @@ export function MemberCard({ c }: { c: Character }) {
<span className="tag">{describeRoots(c.roots)}</span>
{!dead && c.realmProgress >= 100 && <span className="tag gold-t"> · </span>}
{C_STATE[dead ? 'dead' : c.state] && (
<span className={`tag ${C_STATE[dead ? 'dead' : c.state]}`}>{STATE_LABEL[c.state]?.label ?? ''}</span>
<span className={`tag ${C_STATE[dead ? 'dead' : c.state]}`}>{STATE_LABEL[dead ? 'dead' : c.state]?.label ?? ''}</span>
)}
</div>
{!dead && (
+2 -2
View File
@@ -290,8 +290,8 @@ export function MemberModal({ member }: { member: Character }) {
{member.alive && w.canMarry(member.id) && (
<div className="mm-sec">
<div className="dim" style={{ marginBottom: 6 }}></div>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
{w.marriageCandidatesOf(member.id).slice(0, 6).map((cand) => (
<div className="marriage-list">
{w.marriageCandidatesOf(member.id).map((cand) => (
<button
key={cand.id}
className="btn btn-sm"
@@ -0,0 +1,34 @@
import { useGameStore } from '../store'
import { computeUrgency } from '../../game/core/urgency'
export function UrgentBadges() {
const world = useGameStore((s) => s.world)
const revision = useGameStore((s) => s.revision)
const setPanel = useGameStore((s) => s.setPanel)
const setSpeed = useGameStore((s) => s.setSpeed)
void revision
if (!world) return null
const u = computeUrgency(world)
const items: { key: string; label: string; cls: string; onClick: () => void }[] = []
if (u.blockedCount > 0)
items.push({ key: 'block', label: `瓶颈·${u.blockedCount}`, cls: 'badge-safe', onClick: () => { setPanel('family') } })
if (u.injuredCount > 0)
items.push({ key: 'inj', label: `重伤·${u.injuredCount}`, cls: 'badge-bad', onClick: () => setPanel('family') })
if (u.missionCount > 0)
items.push({ key: 'mis', label: `在外·${u.missionCount}`, cls: 'badge-info', onClick: () => setPanel('expedition') })
if (u.marriageReady >= 2)
items.push({ key: 'mar', label: `媒缘·${u.marriageReady}`, cls: 'badge-warn', onClick: () => setPanel('family') })
if (u.noHeir)
items.push({ key: 'heir', label: '孤嗣之忧', cls: 'badge-bad', onClick: () => setPanel('genealogy') })
if (items.length === 0) return null
return (
<div className="urgent-badges">
{items.map((b) => (
<button key={b.key} className={`badge ${b.cls}`} onClick={b.onClick}>
{b.label}
</button>
))}
</div>
)
}