《同福客栈》Phase 1 实施切片 v1.0
设计 → 代码的施工蓝图
基于:《道具词条与玩法扩展 v3.1.4》+《道具系统新核心 UI 视觉规范 v1.0》+《道具系统新核心 UI 视觉稿 v1.0》 范围:Phase 1 = 品质重构 + 词条区间(不包含 §11 槽位 / §12 攻防 / §13 战力) 工期:5 个工作日(每个 T 半天到 1 天,可并行)
0. 元信息
| 项 | 内容 |
| :--- | :--- |
| 文档版本 | v1.0 / 2026-06-29 |
| 配套文档 | v3.1.4 机制 / 视觉规范 v1.0 / 视觉稿 v1.0 |
| 范围(做) | 品质 4 档→9 档重构;词条从字符串 ID 升级到词条库+区间+roll |
| 范围(不做) | §11 槽位 / §12 攻防 / §13 战力(Phase 2);强化/精造等 8 小系统(Phase 3+) |
| 完成标准 | npm run lint 无新增错误;npm run build 通过;现有游戏功能不破坏;9 档品质+词条标签在背包 UI 可见 |
核心原则:
- 每个 T = 1 个 commit / 1 个 PR 颗粒度
- 每个 T 完成后游戏必须能跑、可回滚
- 软迁移(保留旧字段 + 加 TODO)—— Phase 2 才彻底删除
rarity等旧字段
1. 现状盘点
1.1 现有品质系统
| 项 | 现状 | 跟设计差距 |
| :--- | :--- | :--- |
| TS 类型 | rarity: 'common' \| 'rare' \| 'epic' \| 'legendary'(src/types.ts:137) | 4 档,需扩到 9 档 |
| CSS 类 | .rarity-common / .rarity-rare / .rarity-epic / .rarity-mystic(src/index.css) | 4 档 + mystic 私货,需统一到 9 档 |
| 品级放大系数 | 无 | 需新增 1.0x→6.0x 表 |
| 品质色阶规范 | 无正式规范 | 套用视觉规范 v1.0 的 6 档 + 扩 3 档 |
1.2 现有词条系统
| 项 | 现状 | 跟设计差距 |
| :--- | :--- | :--- |
| TS 字段 | prefixId?: string; suffixId?: string;(src/types.ts:140-141) | 仅字符串 ID,无数值/区间/类型 |
| 词条库 | 无集中定义 | 需新建 src/data/affixes.ts |
| 词条类型分类 | 无 | 需分 N/P/S/K/T 5 类(v3.1.4 §3.1) |
| 数值区间 | 无 | 需新增区间常量 |
| rollValue | 无 | 需新增 0.0~1.0 字段 |
| 词条权 | 无 | 需新增 N 1.0 / P 1.5 / S 2.0 / K 2.5 / T 1.0 |
1.3 现有装备模型
- 文件:
src/types.ts:133-151 value: number—— 单一数值,无攻防分离(Phase 2 处理)buildTag?: 'chef' \| 'management' \| 'adventure' \| 'defense'—— 已存在但单选- 无强化/精造字段
- 无品质数值化(品级放大)
1.4 现有 gameData
- 文件:不在
src/下,在工作区根同福客栈挂机游戏/gameData.ts(历史遗留) - 装备示例:~30 件,覆盖 weapon / accessory / ingredient / book
- 全部用旧 4 档 rarity,无 affix / qualityTier / baseStats
1.5 现有拟物化体系(可复用)
| 类名 | 用途 | 设计对应 |
| :--- | :--- | :--- |
| .tongfu-parchment | 宣纸底 | 装备卡底色(视觉规范 §3.1) |
| .tongfu-parchment-light | 浅宣纸 | 详情面板(视觉规范 §4.2) |
| .tongfu-button-wooden | 木质按钮 | 槽位按钮(Phase 2) |
| .tongfu-seal | 朱砂印章 | 装备卡"印"标签(视觉规范 §3.6) |
| .tongfu-crest | 徽章 | 品质徽章(视觉规范 §3.6) |
拟物化体系已就位——宣纸+木+朱砂调性跟视觉规范 v1.0 完全对齐,Phase 1 直接复用,不重做。
2. 8 个任务切片(核心交付物)
| # | 任务 | 工期 | 风险 | 依赖 | | :---: | :--- | :---: | :---: | :---: | | T1 | 品质 9 档系统(类型 + 常量 + 放大系数) | 0.5 天 | 低 | — | | T2 | 词条系统(类型 + 词条库 ~40 条) | 0.5 天 | 低 | — | | T3 | 装备模型字段补齐(EquippableItem v2 + gameData 迁移) | 1 天 | 中 | T1, T2 | | T4 | 9 档色板落地(index.css + @theme 变量) | 0.5 天 | 低 | T1 | | T5 | 装备卡基础组件(QualityFrame + AffixChip) | 1 天 | 低 | T1, T2 | | T6 | 装备卡 UI 升级(沿用现有 BaggageTab 改造) | 0.5 天 | 中 | T3, T4, T5 | | T7 | rollEquipment 工具函数(生成新装备) | 0.5 天 | 低 | T2, T3 | | T8 | 回归测试 + 视觉对照 checklist | 0.5 天 | 低 | T1-T7 |
总工期:5 个工作日
关键路径:T1 → T3 → T6 → T8
可并行:T1+T2 并行;T4+T5 并行(与 T3 同步)
3. 任务依赖图
┌──> T1 (品质类型) ──┐
│ │
T0 (现状) ───────┬──┤ ├──> T3 (装备模型) ──┐
│ └──> T2 (词条类型) ──┘ │
│ │
│ T4 (色板) ──┐ │
│ ├──> T5 (基础组件) ─────┐ │
│ T2 ─────────┘ │ │
│ ├──> T6 (装备卡 UI) ──> T8 (回归)
│ │ ▲
│ │
│ T7 (工具函数) ──────────┘
│
└──> T8 (回归测试,最后跑)
4. 任务详细规格
T1. 品质 9 档系统(类型 + 常量)
目标:把 rarity 从 4 档扩到 9 档,定义品级放大系数表。
改动文件:
src/types.ts—— 新增QualityTier类型src/data/quality.ts(新) —— 9 档常量 + 放大系数 + 元数据
新增类型(src/types.ts):
/**
* 品质 9 档(Phase 1 重构引入)
* 旧 'common' | 'rare' | 'epic' | 'legendary' 保留做软迁移,Phase 2 删除
*/
export type QualityTier =
| 'waste' // Q1 白板
| 'common' // Q2 普通
| 'fine' // Q3 优秀
| 'rare' // Q4 稀有
| 'epic' // Q5 史诗
| 'legend' // Q6 传说
| 'mythic' // Q7 神话
| 'divine' // Q8 神器
| 'celestial'; // Q9 不朽
⚠️ 注意:
common名字复用,但语义变了(Q2 普通 vs 旧 common)。下游需要按 import path 区分。
新增常量(src/data/quality.ts):
import type { QualityTier } from '@/types';
/**
* 品质档位元数据(Phase 1)
* 与 v3.1.4 §2 品质体系 + 视觉规范 v1.0 §2.1 色组对应
*/
export const QUALITY_TIERS: Record<QualityTier, {
/** 顺序(1-9) */
rank: number;
/** 中文名 */
name: string;
/** 单字徽章(显示在 tongfu-seal 上) */
shortName: string;
/** 品级放大系数(v3.1.4 §13 战力公式用) */
multiplier: number;
/** 对应 CSS 变量(@theme --color-q-*) */
cssVar: string;
/** 默认词条数 */
affixCount: number;
/** rollValue 区间(0.0~1.0,决定词条数值倍率) */
affixRoll: [number, number];
}> = {
waste: { rank: 1, name: '白板', shortName: '白', multiplier: 1.0, cssVar: '--color-q-waste', affixCount: 0, affixRoll: [0.0, 0.0] },
common: { rank: 2, name: '普通', shortName: '普', multiplier: 1.3, cssVar: '--color-q-common', affixCount: 1, affixRoll: [0.0, 0.3] },
fine: { rank: 3, name: '优秀', shortName: '优', multiplier: 1.7, cssVar: '--color-q-fine', affixCount: 1, affixRoll: [0.3, 0.5] },
rare: { rank: 4, name: '稀有', shortName: '稀', multiplier: 2.2, cssVar: '--color-q-rare', affixCount: 2, affixRoll: [0.4, 0.6] },
epic: { rank: 5, name: '史诗', shortName: '史', multiplier: 2.9, cssVar: '--color-q-epic', affixCount: 2, affixRoll: [0.5, 0.7] },
legend: { rank: 6, name: '传说', shortName: '传', multiplier: 3.8, cssVar: '--color-q-legend', affixCount: 3, affixRoll: [0.6, 0.8] },
mythic: { rank: 7, name: '神话', shortName: '神', multiplier: 4.7, cssVar: '--color-q-mythic', affixCount: 3, affixRoll: [0.7, 0.9] },
divine: { rank: 8, name: '神器', shortName: '器', multiplier: 5.5, cssVar: '--color-q-divine', affixCount: 4, affixRoll: [0.8, 1.0] },
celestial: { rank: 9, name: '不朽', shortName: '朽', multiplier: 6.0, cssVar: '--color-q-celestial', affixCount: 4, affixRoll: [0.9, 1.0] },
};
/** 按 rank 排序的品质数组(用于色板对照 / 下拉选择) */
export const QUALITY_ORDER: QualityTier[] = [
'waste', 'common', 'fine', 'rare', 'epic',
'legend', 'mythic', 'divine', 'celestial',
];
/** 工具:按 rank 取品质 */
export function getQualityRank(tier: QualityTier): number {
return QUALITY_TIERS[tier].rank;
}
验收:
npm run lint通过- 旧的
EquippableItem.rarity字段引用不报错(这版本不改 EquippableItem,T3 才动) - 新增类型
QualityTier在 storybook/test 页可用
T2. 词条系统(类型 + 词条库)
目标:定义词条类型 + 首批 ~40 个词条(覆盖 5 大类型 + 4 大流派)。
改动文件:
src/types.ts—— 新增Affix/AffixKind等src/data/affixes.ts(新) —— 首批 ~40 个词条
新增类型(src/types.ts):
/**
* 词条类型(v3.1.4 §3.1)
* N = Numeric 数值类(+X 直接加成)
* P = Percentage 百分比(+X% 比率加成)
* S = Special 特殊(独立机制,如铜臭/旺财)
* K = Key 关键(重要标识,如传承/好运)
* T = Triggered 触发(条件触发,如怒意/韧性)
*/
export type AffixKind = 'N' | 'P' | 'S' | 'K' | 'T';
/** 词条权(v3.1.4 §13 战力公式用) */
export const AFFIX_WEIGHT: Record<AffixKind, number> = {
N: 1.0,
P: 1.5,
S: 2.0,
K: 2.5,
T: 1.0,
};
/** 词条影响的统计字段 */
export type AffixStat =
// 攻防效(Phase 2 §12 消费)
| 'atk' | 'def' | 'eff'
// 员工生命
| 'hp' | 'stamina' | 'mood'
// 收益
| 'coinBonus' | 'repBonus'
// 战斗(未来)
| 'critRate' | 'critDmg' | 'dodgeRate' | 'speed';
/**
* 词条定义(词条库里的"模板")
* 实例化时需要 rollValue(决定具体数值)+ 区间
*/
export interface Affix {
id: string; // 词条 ID,如 'affix_atk_flat'
kind: AffixKind; // 类型
stat: AffixStat; // 影响字段
name: string; // 中文名,如 "锋利"
desc: string; // 描述模板(含 {value} 占位)
valueRange: [number, number]; // 数值区间下限/上限
valueUnit: 'flat' | 'pct'; // flat 加数值 / pct 加百分比
weight: number; // 抽取权重(默认 1,越大越容易出)
tags?: ('chef' | 'management' | 'adventure' | 'defense')[]; // 流派过滤
}
/**
* 实例化后的词条(装备上的"具体词条")
* 带 rollValue(0.0~1.0 决定数值倍率)和最终数值
*/
export interface RolledAffix extends Affix {
rollValue: number; // 0.0~1.0
rolledValue: number; // 实际数值 = lerp(valueRange[0], valueRange[1], rollValue)
}
首批词条库(src/data/affixes.ts)—— ~40 个词条,分 5 类 + 4 流派 tag:
N 类(数值,10 条):
{ id: 'affix_atk_flat', kind: 'N', stat: 'atk', name: '锋利', desc: '攻击 +{value}',
valueRange: [5, 15], valueUnit: 'flat', weight: 1.0, tags: ['chef', 'defense'] },
{ id: 'affix_def_flat', kind: 'N', stat: 'def', name: '坚固', desc: '防御 +{value}',
valueRange: [3, 10], valueUnit: 'flat', weight: 1.0, tags: ['defense'] },
{ id: 'affix_eff_flat', kind: 'N', stat: 'eff', name: '麻利', desc: '效率 +{value}',
valueRange: [2, 8], valueUnit: 'flat', weight: 1.0, tags: ['chef', 'management'] },
{ id: 'affix_hp_flat', kind: 'N', stat: 'hp', name: '血气', desc: '生命 +{value}',
valueRange: [10, 30], valueUnit: 'flat', weight: 1.0, tags: ['adventure', 'defense'] },
{ id: 'affix_stamina_flat', kind: 'N', stat: 'stamina', name: '耐力', desc: '体力 +{value}',
valueRange: [5, 15], valueUnit: 'flat', weight: 1.0, tags: ['adventure'] },
// ... 还有 5 条:好运/巧手/精准/敏捷/沉稳
P 类(百分比,10 条):
{ id: 'affix_atk_pct', kind: 'P', stat: 'atk', name: '凶猛', desc: '攻击 +{value}%',
valueRange: [5, 15], valueUnit: 'pct', weight: 1.0, tags: ['chef', 'defense'] },
{ id: 'affix_def_pct', kind: 'P', stat: 'def', name: '坚韧', desc: '防御 +{value}%',
valueRange: [5, 15], valueUnit: 'pct', weight: 1.0, tags: ['defense'] },
{ id: 'affix_speed_pct', kind: 'P', stat: 'speed', name: '疾风', desc: '速度 +{value}%',
valueRange: [5, 15], valueUnit: 'pct', weight: 1.0, tags: ['chef', 'management'] },
// ... 还有 7 条
S 类(特殊,8 条):
{ id: 'affix_coin_flat', kind: 'S', stat: 'coinBonus', name: '铜臭', desc: '铜钱/时辰 +{value}',
valueRange: [10, 30], valueUnit: 'flat', weight: 1.0, tags: ['management'] },
{ id: 'affix_coin_pct', kind: 'S', stat: 'coinBonus', name: '旺财', desc: '收益 +{value}%',
valueRange: [5, 15], valueUnit: 'pct', weight: 1.0, tags: ['management'] },
// ... 还有 6 条
K 类(关键,6 条):
{ id: 'affix_journey_xp', kind: 'K', stat: 'repBonus', name: '传承', desc: '阅历/天 +{value}',
valueRange: [1, 3], valueUnit: 'flat', weight: 1.0 },
{ id: 'affix_lucky', kind: 'K', stat: 'critRate', name: '好运', desc: '暴击率 +{value}%',
valueRange: [3, 8], valueUnit: 'pct', weight: 1.0 },
// ... 还有 4 条
T 类(触发,6 条):
{ id: 'affix_fury', kind: 'T', stat: 'atk', name: '怒意', desc: 'HP<30% 时攻击 +{value}%',
valueRange: [10, 30], valueUnit: 'pct', weight: 1.0, tags: ['defense', 'adventure'] },
{ id: 'affix_resilience', kind: 'T', stat: 'def', name: '韧性', desc: '受击时减伤 +{value}%',
valueRange: [5, 15], valueUnit: 'pct', weight: 1.0, tags: ['defense'] },
// ... 还有 4 条
汇总:N 10 + P 10 + S 8 + K 6 + T 6 = 40 个词条
验收:
npm run lint通过src/data/affixes.ts至少 40 个词条- 区间合理:数值类 ±50%、百分比类 ±5%
- 每个词条都有
id / kind / stat / name / desc / valueRange / valueUnit / weight
T3. 装备模型字段补齐(EquippableItem v2)
目标:在 EquippableItem 上加 qualityTier / affixes / baseStats / 升级 buildTag。
改动文件:
src/types.ts—— 修改EquippableItem字段同福客栈挂机游戏/gameData.ts(根目录历史遗留) —— 迁移 ~30 件装备
修改后(src/types.ts,行 133-151):
export interface EquippableItem {
kind?: 'equipment';
id: string;
name: string;
/** 新:品质 9 档(Phase 1 引入,替代旧的 rarity) */
qualityTier: QualityTier;
/** 旧:保留做软迁移,Phase 2 删除(标 TODO) */
// TODO: Phase 2 删除
rarity: 'common' | 'rare' | 'epic' | 'legendary';
type: 'weapon' | 'accessory' | 'ingredient' | 'book';
baseId: string;
prefixId?: string;
suffixId?: string;
/** 新:实例化词条(0~4 个) */
affixes: RolledAffix[];
/** 新:基础三维(Phase 2 §12 消费) */
baseStats: {
atk?: number;
def?: number;
eff?: number;
};
/** 新:单一数值保留(兼容旧显示) */
stat: 'cookingSpeed' | 'cleanPower' | 'security' | 'incomeMultiplier' | 'passiveIncome';
value: number;
level: number;
/** 升级:buildTag 改多选 */
buildTag: ('chef' | 'management' | 'adventure' | 'defense')[];
recommendedStaff?: StaffId[];
equippedTo?: StaffId | null;
flavorText: string;
isSkillScroll?: boolean;
isLocked?: boolean;
}
gameData.ts 迁移示例(每件装备补 4 个字段):
// 旧:
{ id: 'wok_iron', name: '铁锅', rarity: 'common', type: 'weapon', value: 50, ... }
// 新(保留 rarity,加 qualityTier/affixes/baseStats/buildTag 数组):
{
id: 'wok_iron', name: '铁锅',
rarity: 'common', // 保留软迁移
qualityTier: 'common', // 新
affixes: [ // 新:示例词条
{ id: 'affix_atk_flat', kind: 'N', stat: 'atk', name: '锋利', desc: '...',
valueRange: [5,15], valueUnit: 'flat', weight: 1,
rollValue: 0.4, rolledValue: 9 },
],
baseStats: { atk: 12, def: 2, eff: 5 }, // 新
// ... 其他字段
buildTag: ['chef', 'management'], // 升级为数组
}
30 件装备迁移策略:
- 白板装备(10 件):
qualityTier: 'waste',affixes: [] - 普通装备(10 件):
qualityTier: 'common',1 个词条 - 优秀装备(5 件):
qualityTier: 'fine',1 个词条(rollValue 偏高) - 稀有装备(3 件):
qualityTier: 'rare',2 个词条 - 史诗装备(2 件):
qualityTier: 'epic',2 个词条
验收:
npm run lint通过- 30 件装备全部迁移
- 打开游戏 → 背包能正常显示(旧字段仍在,不报错)
T4. 9 档色板落地(index.css)
目标:把视觉规范 v1.0 §2.1 的 6 档色 + 扩 3 档,落到 CSS @theme。
改动文件:
src/index.css—— 新增@theme变量 + 9 档品质类 + 不朽档专属动效
新增 CSS(追加到 @theme {} 块后):
@theme {
/* ── 9 档品质色(与 src/data/quality.ts 对应)── */
--color-q-waste: #9ca3af; /* Q1 白板:冷灰 */
--color-q-common: #6b7280; /* Q2 普通:暖灰 */
--color-q-fine: #84cc16; /* Q3 优秀:黄绿 */
--color-q-rare: #06b6d4; /* Q4 稀有:青蓝 */
--color-q-epic: #a855f7; /* Q5 史诗:紫 */
--color-q-legend: #f59e0b; /* Q6 传说:金 */
--color-q-mythic: #ef4444; /* Q7 神话:朱红 */
--color-q-divine: #ec4899; /* Q8 神器:洋红 */
--color-q-celestial-from: #fbbf24;
--color-q-celestial-mid: #ef4444;
--color-q-celestial-to: #a855f7;
}
/* ── 9 档品质通用类(与视觉规范 v1.0 §2.1 对照) ── */
.rarity-waste { color: var(--color-q-waste); border-color: var(--color-q-waste); }
.rarity-common { color: var(--color-q-common); border-color: var(--color-q-common); }
.rarity-fine { color: var(--color-q-fine); border-color: var(--color-q-fine); }
.rarity-rare { color: var(--color-q-rare); border-color: var(--color-q-rare); }
.rarity-epic { color: var(--color-q-epic); border-color: var(--color-q-epic); }
.rarity-legend { color: var(--color-q-legend); border-color: var(--color-q-legend); }
.rarity-mythic { color: var(--color-q-mythic); border-color: var(--color-q-mythic); }
.rarity-divine { color: var(--color-q-divine); border-color: var(--color-q-divine); }
/* 不朽档(Q9)专属:金光渐变 + 脉动 */
.rarity-celestial {
background: linear-gradient(
135deg,
var(--color-q-celestial-from) 0%,
var(--color-q-celestial-mid) 50%,
var(--color-q-celestial-to) 100%
);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
border-color: var(--color-q-celestial-from);
font-weight: 800;
}
@keyframes celestial-shimmer {
0%, 100% { filter: brightness(1.0) drop-shadow(0 0 4px rgba(251,191,36,0.4)); }
50% { filter: brightness(1.2) drop-shadow(0 0 12px rgba(251,191,36,0.8)); }
}
.rarity-celestial-card {
animation: celestial-shimmer 3s ease-in-out infinite;
}
/* ── 旧 4 档保留(兼容性,Phase 2 才删) ── */
.rarity-common-legacy { /* 旧 common 灰,保持原样 */ }
.rarity-rare-legacy { /* 旧 rare 蓝,保持原样 */ }
.rarity-epic-legacy { /* 旧 epic 紫,保持原样 */ }
.rarity-mystic { /* 保留特殊 mystic(金光脉动) */ }
验收:
- 在 dev/storybook 页用 9 个
<div class="rarity-{tier}">测试</div>验证色对 - 不朽档有金光渐变 + 脉动
- 旧的
.rarity-common等不报错(兼容)
T5. 装备卡基础组件(QualityFrame + AffixChip)
目标:抽出 2 个新组件,可复用到装备卡 / 详情面板 / 列表。
新建文件:
src/components/item/QualityFrame.tsx—— 品质外框src/components/item/AffixChip.tsx—— 词条标签src/components/item/index.ts—— 统一导出
QualityFrame.tsx(对应视觉规范 §3.1 装备卡组件):
import type { ReactNode } from 'react';
import { QUALITY_TIERS, type QualityTier } from '@/data/quality';
interface Props {
tier: QualityTier;
children: ReactNode;
className?: string;
/** 是否启用卡片级脉动(仅不朽档默认开) */
shimmer?: boolean;
}
export const QualityFrame: React.FC<Props> = ({
tier,
children,
className = '',
shimmer,
}) => {
const meta = QUALITY_TIERS[tier];
const enableShimmer = shimmer ?? tier === 'celestial';
return (
<div
className={[
'relative rounded-lg border-2 p-2 transition-shadow',
`rarity-${tier}`,
enableShimmer ? 'rarity-celestial-card' : '',
className,
].filter(Boolean).join(' ')}
data-tier={tier}
data-quality-rank={meta.rank}
>
{/* 品质徽章:右上角小印 */}
<span
className="tongfu-seal absolute -top-2 -right-2 z-10"
title={meta.name}
aria-label={`品质 ${meta.name}`}
>
{meta.shortName}
</span>
{children}
</div>
);
};
AffixChip.tsx(对应视觉规范 §3.5 词条标签):
import type { Affix, RolledAffix, AffixKind } from '@/types';
const KIND_STYLE: Record<AffixKind, string> = {
N: 'bg-slate-100 text-slate-700 border-slate-300', // 数值:素
P: 'bg-cyan-100 text-cyan-800 border-cyan-300', // 百分比:冷
S: 'bg-amber-100 text-amber-800 border-amber-300', // 特殊:暖
K: 'bg-purple-100 text-purple-800 border-purple-300', // 关键:贵
T: 'bg-rose-100 text-rose-800 border-rose-300', // 触发:烈
};
interface Props {
affix: Affix | RolledAffix;
/** 实例化后的实际数值(RollAffix.rolledValue) */
rolledValue?: number;
compact?: boolean; // 紧凑模式(卡片内用)
}
export const AffixChip: React.FC<Props> = ({ affix, rolledValue, compact = false }) => {
const value = rolledValue ?? (affix as RolledAffix).rolledValue;
const display = affix.valueUnit === 'pct'
? `+${(value * 100).toFixed(1)}%`
: `+${value.toFixed(0)}`;
return (
<span
className={[
'inline-flex items-center gap-1 rounded border font-medium',
compact ? 'text-[10px] px-1 py-0' : 'text-xs px-1.5 py-0.5',
KIND_STYLE[affix.kind],
].join(' ')}
data-affix-id={affix.id}
data-affix-kind={affix.kind}
>
<span className="font-bold">{affix.name}</span>
<span className="opacity-80">{display}</span>
</span>
);
};
index.ts:
export { QualityFrame } from './QualityFrame';
export { AffixChip } from './AffixChip';
验收:
- 在 dev/storybook 页手动
<QualityFrame tier="mythic">测试</QualityFrame>验证 9 档色 - 不朽档有金光脉动
- AffixChip 5 种 kind 颜色正确(素/冷/暖/贵/烈)
T6. 装备卡 UI 升级(沿用现有 BaggageTab 改造)
目标:现有 BaggageTab 中的装备卡升级——从「4 档 rarity」升级到「9 档 QualityFrame + AffixChip」。
改动文件:
src/components/BaggageTab.tsx(或 item 目录里新建 ItemCard.tsx 后引用)
改造点:
- 装备卡外层:
// 旧:
<div className={`item-card rarity-${item.rarity}`}>
{/* ... */}
</div>
// 新:
<QualityFrame tier={item.qualityTier ?? 'waste'} className="item-card">
{/* ... */}
</QualityFrame>
- 词条区域:
// 新增(在装备卡底部):
{item.affixes?.length > 0 && (
<div className="mt-1.5 flex flex-wrap gap-1">
{item.affixes.map((affix, i) => (
<AffixChip key={i} affix={affix} rolledValue={affix.rolledValue} compact />
))}
</div>
)}
-
保留旧视觉元素:图标 / 名称 / 装备按钮 / 锁定按钮(不变)
-
Fallback:如果装备没有
qualityTier(极端旧数据),fallback 到waste档
验收:
- 打开游戏 → 进背包 → 看到 30 件装备按 9 档品质上色
- 词条标签在卡片里可见
- buildTag 仍正确显示(数组化后不破版)
- 装备/卸下/锁定按钮功能不变
T7. rollEquipment 工具函数
目标:根据品质 + 词条库,随机生成一件新装备。
新建文件:
src/utils/itemGen.ts
API 设计:
import type { Affix, QualityTier, EquippableItem, StaffId } from '@/types';
import { QUALITY_TIERS } from '@/data/quality';
import { AFFIX_POOL } from '@/data/affixes';
export interface RollOptions {
tier: QualityTier; // 品质
type: EquippableItem['type']; // 类型
buildTag?: EquippableItem['buildTag'][number]; // 流派过滤
affixPool?: Affix[]; // 自定义词条池(默认用全库)
rng?: () => number; // 注入随机数(测试用)
}
/**
* 根据品质和流派随机生成一件装备
* Phase 1 实现:基础三维 + 词条抽取 + rollValue 计算
* Phase 2 扩展:装备战力计算
*/
export function rollEquipment(opts: RollOptions): EquippableItem {
const {
tier,
type,
buildTag,
affixPool = AFFIX_POOL,
rng = Math.random,
} = opts;
const meta = QUALITY_TIERS[tier];
const pool = buildTag
? affixPool.filter(a => !a.tags || a.tags.length === 0 || a.tags.includes(buildTag))
: affixPool;
// 1. 抽取词条(按权重 + 词条数)
const affixes: RolledAffix[] = [];
const usedIds = new Set<string>();
for (let i = 0; i < meta.affixCount; i++) {
const candidates = pool.filter(a => !usedIds.has(a.id));
if (candidates.length === 0) break;
const picked = weightedPick(candidates, a => a.weight, rng);
usedIds.add(picked.id);
// 2. 计算 rollValue(在品质区间内)
const [rollMin, rollMax] = meta.affixRoll;
const rollValue = rollMin + rng() * (rollMax - rollMin);
const [vMin, vMax] = picked.valueRange;
const rolledValue = vMin + rollValue * (vMax - vMin);
affixes.push({ ...picked, rollValue, rolledValue });
}
// 3. 基础三维(按 type 推默认值)
const baseStats = generateBaseStats(type, tier);
// 4. 组装 EquippableItem(其他字段用占位)
return {
kind: 'equipment',
id: `gen_${Date.now()}_${Math.floor(rng() * 100000)}`,
name: `${meta.name}级${typeLabel(type)}`,
qualityTier: tier,
rarity: tierToOldRarity(tier), // 软迁移
type,
baseId: `base_${type}`,
affixes,
baseStats,
stat: defaultStat(type),
value: sumBaseStats(baseStats) * meta.multiplier,
level: 1,
buildTag: buildTag ? [buildTag] : [],
flavorText: `${meta.name}品质的${typeLabel(type)},附 ${affixes.length} 条词条。`,
};
}
// 内部工具:加权随机抽取
function weightedPick<T>(items: T[], weight: (item: T) => number, rng: () => number): T {
const total = items.reduce((s, x) => s + weight(x), 0);
let r = rng() * total;
for (const item of items) {
r -= weight(item);
if (r <= 0) return item;
}
return items[items.length - 1];
}
// 内部工具:基础三维默认值(按 type + tier)
function generateBaseStats(type: EquippableItem['type'], tier: QualityTier): { atk?: number; def?: number; eff?: number } {
const base = 5 + QUALITY_TIERS[tier].rank * 2;
switch (type) {
case 'weapon': return { atk: base };
case 'accessory': return { def: Math.floor(base * 0.6), eff: Math.floor(base * 0.4) };
case 'ingredient': return { eff: base };
case 'book': return { eff: base, def: Math.floor(base * 0.3) };
}
}
// 内部工具:求和基础三维
function sumBaseStats(s: { atk?: number; def?: number; eff?: number }): number {
return (s.atk ?? 0) + (s.def ?? 0) + (s.eff ?? 0);
}
验收:
- 单元测试:固定 rng → 输出确定性(
rollValue === 0.5) - 单元测试:品质 =
mythic+buildTag = 'chef'→ 词条至少 1 个 chef tag - 单元测试:词条数 ===
QUALITY_TIERS[tier].affixCount - 单元测试:词条数值在
valueRange内
T8. 回归测试 + 视觉对照 checklist
目标:确保 T1-T7 改完后没破坏现有功能,且 9 档色板跟视觉稿 v1.0 一致。
新建文件:
docs/design/active/Phase1实施切片-v1-checklist.md
checklist 内容:
# Phase 1 实施回归 Checklist
> 跑这个 checklist 前:所有 T1-T7 的 commit 都已合并到 main
## A. 代码质量
- [ ] `npm run lint` 通过(tsc --noEmit 无新增错误)
- [ ] `npm run build` 通过
- [ ] 旧 4 档 rarity 引用全部带 `// TODO: Phase 2` 注释
- [ ] 无新增 `any` 类型(除非有显式注释)
## B. 视觉对照(跟 HTML 视觉稿 v1.0 对)
- [ ] 9 档品质色全对(白灰/暖灰/黄绿/青蓝/紫/金/朱红/洋红/金光渐变)
- [ ] 不朽档有金光渐变 + 3s 脉动
- [ ] AffixChip 5 种 kind 颜色(素/冷/暖/贵/烈)跟视觉稿对
- [ ] 拟物化调性保留:tongfu-parchment / tongfu-seal / tongfu-crest 仍生效
- [ ] 装备卡在 360 / 768 / 1440 三个断点下不破版
## C. 功能完整性
- [ ] 30 件装备迁移后无数据丢失
- [ ] 装备/卸下/锁定功能正常
- [ ] buildTag 多选后过滤仍正常(如果旧代码用单选)
- [ ] localStorage 存档兼容(用旧档打开不报错)
## D. 性能
- [ ] 背包 100 件装备渲染 < 100ms(用 React Profiler 验证)
- [ ] rollEquipment 单次调用 < 5ms
## E. 手动测试场景
1. 开新档 → 进游戏 → 进背包 → 看到 30 件装备(9 档色按概率分布)
2. 点击一件装备 → 详情面板显示词条
3. 装备/卸下 → 装备关系正确
4. 跨页面刷新(localStorage) → 数据不丢
5. 极端场景:装备词条 4 个 + 不朽品质 → 卡片不被截断
6. rollEquipment(legend, weapon, chef) × 10 次 → 输出 10 件不同装备
T8 本身执行:把所有 checkbox 跑一遍,失败的回滚对应 T,重做。
5. 验收清单(汇总)
每个 T 完成后都跑一遍:
npm run lint # tsc --noEmit
npm run build # vite build
npm run dev # 启动后手动玩一遍:开档 → 进游戏 → 进背包 → 看装备
T8 完成后跑完整 checklist(见 §4 T8)。
6. 回滚方案
每个 T 都是独立 commit(git revert 单个 PR 即可回滚)。
| T | 回滚方式 | 风险 |
| :-: | :--- | :--- |
| T1 | git revert T1-commit —— qualityTier 类型删除,下游不引用 | 低 |
| T2 | git revert T2-commit —— Affix 类型删除,affixes.ts 删除 | 低 |
| T3 | 还原 同福客栈挂机游戏/gameData.ts:git checkout T3^ -- 同福客栈挂机游戏/gameData.ts + revert types.ts 改动 | 中(数据迁移可能丢) |
| T4 | 还原 src/index.css:git checkout T4^ -- src/index.css | 低 |
| T5 | git revert T5-commit —— 新组件目录删除 | 低 |
| T6 | 还原 src/components/BaggageTab.tsx:git checkout T6^ -- src/components/BaggageTab.tsx | 中(可能丢失装备卡改进) |
| T7 | git revert T7-commit —— itemGen.ts 删除 | 低 |
| T8 | 删除 checklist 文件 | 低 |
核心回滚原则:T1/T2/T4/T5/T7 是纯增量,回滚零风险;T3/T6 是改造,回滚时需要还原数据/UI 文件。
7. 与设计文档的对应
| 切片任务 | 设计文档章节 | | :--- | :--- | | T1 品质 9 档 | v3.1.4 §2 品质体系 | | T2 词条系统 | v3.1.4 §3 词条机制(含 §3.1 类型 / §3.2 区间 / §3.3 rollValue) | | T3 装备模型 | v3.1.4 §2.5 装备生成规则 | | T4 9 档色板 | 视觉规范 v1.0 §2.1 品质色组 + §3.1 装备卡组件 | | T5 基础组件 | 视觉规范 v1.0 §3 核心组件规格 | | T6 装备卡 UI | 视觉规范 v1.0 §4.1 装备详情界面 | | T7 rollEquipment | v3.1.4 §2.5 + §3.3 词条 roll 机制 | | T8 回归测试 | 视觉稿 v1.0 全局对照 |
配套文档索引(v3.1.4 附录 B 新增):
《道具词条与玩法扩展 v3.1.4》 ← 机制源头
《道具系统新核心 UI 视觉规范 v1.0》 ← 视觉契约
《道具系统新核心 UI 视觉稿 v1.html》 ← 视觉展示
《Phase 1 实施切片 v1.0》 ← 施工蓝图(本文件)
8. 后续 Phase 预告(不在本切片范围)
| Phase | 范围 | 设计依据 | 预计工期 | | :---: | :--- | :--- | :---: | | Phase 2 | §11 槽位 + §12 攻防 + §13 战力 | v3.1.4 §11/§12/§13 | 10-15 天 | | Phase 3 | §14 装备强化 + §15 员工换装 | v3.1.4 §14/§15 | 5-7 天 | | Phase 4 | §16 装备获得 + §17 背包管理 | 待补设计 | 5-7 天 |
Phase 1 完工后,下一步直接进入 Phase 2——届时所有"展示"基础设施就位,新功能直接对接 §11/§12/§13 的数据模型。
9. 实施节奏建议
| 工作日 | 任务 | 备注 | | :---: | :--- | :--- | | 第 1 天 上午 | T1 品质 9 档类型 + 常量 | 半天搞定 | | 第 1 天 下午 | T2 词条系统 + 词条库 | 半天搞定 | | 第 2 天 上午 | T3 装备模型字段补齐 | types.ts 改完 | | 第 2 天 下午 | T3 迁移 gameData 30 件装备 + T4 9 档色板 | 并行做 | | 第 3 天 | T5 QualityFrame + AffixChip 组件 | 独立日 | | 第 4 天 上午 | T6 装备卡 UI 改造 | 半天 | | 第 4 天 下午 | T7 rollEquipment 工具函数 | 半天 | | 第 5 天 | T8 回归测试 + checklist | 收尾 |
每天 commit 一次,PR 互相独立可 review。
附录 A. 设计哲学速记(避免实施时漂移)
- 拟物化 —— 宣纸做底、墨色做字、朱砂做强调;不要纯白/纯黑/霓虹色
- 视觉降噪 —— 5 秒决策铁律(每屏只解决 1 个决策)+ 1 焦点铁律(同屏只 1 个视觉焦点)
- 客栈调性 —— 三主色上限(宣纸/墨色/朱砂);装备卡像"宣纸+印章",槽位像"木质相框"(Phase 2)
- 新手友好 —— 5 秒看懂"为什么这件强"(词条可视化)+ 3 秒看懂"换不换"(Phase 2 替换对比)
Phase 1 实施切片 v1.0 完。 等你拍板开 T1,还是先讨论切片本身?